Imports System.Drawing
Imports System.Windows.Forms

Imports Pilz.Win32

Friend Class HighlightPanel
    Inherits Control

    Private _Highlights As Dictionary(Of Control, eHighlightColor) = Nothing
    Private _HighlightRegions As List(Of HighlightRegion) = New List(Of HighlightRegion)()

    Public Sub New(ByVal highlights As Dictionary(Of Control, eHighlightColor))
        _Highlights = highlights
        Me.SetStyle(ControlStyles.UserPaint, True)
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        Me.SetStyle(ControlStyles.Opaque, True)
        Me.SetStyle(ControlStyles.ResizeRedraw, True)
        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
        Me.SetStyle(ControlStyles.Selectable, False)
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias

        For Each highlightRegion As HighlightRegion In _HighlightRegions
            Dim colors As Color() = GetHighlightColors(highlightRegion.HighlightColor)
            Dim r As Rectangle = highlightRegion.Bounds
            Dim back As Color = highlightRegion.BackColor
            r.Inflate(1, 1)
            DisplayHelp.FillRectangle(g, r, back)
            r.Inflate(-1, -1)
            DisplayHelp.FillRoundedRectangle(g, r, 2, colors(0))
            r.Inflate(-2, -2)
            DisplayHelp.DrawRectangle(g, colors(2), r)
            r.Inflate(1, 1)
            DisplayHelp.DrawRoundedRectangle(g, colors(1), r, 2)
        Next

        MyBase.OnPaint(e)
    End Sub

    Private Function GetHighlightColors(ByVal color As eHighlightColor) As Color()
        Dim colors As Color() = New Color(2) {}

        If color = eHighlightColor.Blue Then
            colors(0) = GetColor(172, &H6A9CD4)
            colors(1) = GetColor(&H6A9CD4)
            colors(2) = GetColor(&H5D7EA4)
        ElseIf color = eHighlightColor.Orange Then
            colors(0) = GetColor(172, &HFF9C00)
            colors(1) = GetColor(&HFF9C00)
            colors(2) = GetColor(&HCC6600)
        ElseIf color = eHighlightColor.Green Then
            colors(0) = GetColor(172, &H71B171)
            colors(1) = GetColor(&H71B171)
            colors(2) = GetColor(&H339933)
        ElseIf color = eHighlightColor.Custom Then
            If _CustomHighlightColors Is Nothing OrElse _CustomHighlightColors.Length < 3 Then
                colors(0) = System.Drawing.Color.Red
                colors(1) = System.Drawing.Color.Red
                colors(2) = System.Drawing.Color.Red
            Else
                colors(0) = _CustomHighlightColors(0)
                colors(1) = _CustomHighlightColors(1)
                colors(2) = _CustomHighlightColors(2)
            End If
        Else
            colors(0) = GetColor(172, &HC63030)
            colors(1) = GetColor(&HC63030)
            colors(2) = GetColor(&H990000)
        End If

        Return colors
    End Function

    Protected Overrides Sub OnVisibleChanged(ByVal e As EventArgs)
        If Me.Visible AndAlso Not _UpdatingRegion Then UpdateRegion()
        MyBase.OnVisibleChanged(e)
    End Sub

    Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
        If Not _RegionInitialized Then UpdateRegion()
        MyBase.OnHandleCreated(e)
    End Sub

    Private _RegionInitialized As Boolean = False
    Private _UpdatingRegion As Boolean = False

    Friend Sub UpdateRegion()
        If _UpdatingRegion OrElse Not Me.IsHandleCreated Then Return

        Try
            _UpdatingRegion = True
            Me.Region = Nothing
            _HighlightRegions.Clear()
            If _Highlights Is Nothing Then Return

            If _Highlights.Count = 0 AndAlso _FocusHighlightControl Is Nothing Then
                Me.Visible = False
                Return
            End If

            Dim processFocusControl As Boolean = True
            Dim region As Region = Nothing

            For Each item As KeyValuePair(Of Control, eHighlightColor) In _Highlights
                If item.Value = eHighlightColor.None OrElse Not GetIsVisible(item.Key) Then Continue For
                If item.Key Is _FocusHighlightControl Then processFocusControl = False
                Dim r As Rectangle = GetControlRect(item.Key)
                If r.IsEmpty Then Continue For
                r.Inflate(2, 2)
                _HighlightRegions.Add(New HighlightRegion(r, GetBackColor(item.Key.Parent), item.Value))

                If region Is Nothing Then
                    region = New Region(r)
                Else
                    region.Union(r)
                End If

                r.Inflate(-3, -3)
                region.Exclude(r)
            Next

            If processFocusControl AndAlso _FocusHighlightControl IsNot Nothing AndAlso _FocusHighlightControl.Visible Then
                Dim r As Rectangle = GetControlRect(_FocusHighlightControl)

                If Not r.IsEmpty Then
                    r.Inflate(2, 2)
                    _HighlightRegions.Add(New HighlightRegion(r, GetBackColor(_FocusHighlightControl.Parent), _FocusHighlightColor))

                    If region Is Nothing Then
                        region = New Region(r)
                    Else
                        region.Union(r)
                    End If

                    r.Inflate(-3, -3)
                    region.Exclude(r)
                End If
            End If

            Me.Region = region

            If region Is Nothing Then
                Me.Visible = False
            ElseIf Not Me.Visible Then
                Me.Visible = True
                Me.BringToFront()
            End If

            Me.Invalidate()
        Finally
            _UpdatingRegion = False
            _RegionInitialized = True
        End Try
    End Sub

    Private Shared Function GetColor(rgb As Integer) As Color
        If rgb = -1 Then
            Return Color.Empty
        Else
            Return Color.FromArgb((rgb And &HFF0000) >> 16, (rgb And &HFF00) >> 8, rgb And &HFF)
        End If
    End Function

    Private Shared Function GetColor(alpha As Integer, rgb As Integer) As Color
        If rgb = -1 Then
            Return Color.Empty
        Else
            Return Color.FromArgb(alpha, (rgb And &HFF0000) >> 16, (rgb And &HFF00) >> 8, rgb And &HFF)
        End If
    End Function

    Private Function GetIsVisible(ByVal control As Control) As Boolean
        If Not control.Visible Then Return False
        If control.Parent Is Nothing OrElse Not control.IsHandleCreated Then Return control.Visible
        Dim rect As New Native.RECT
        Native.User32.GetWindowRect(control.Handle, rect)
        Dim pp As Point = control.Parent.PointToClient(New Point(rect.Left + 3, rect.Top + 3))
        Dim handle As IntPtr = Native.User32.ChildWindowFromPointEx(control.Parent.Handle, New Native.POINT(pp.X, pp.Y), CUInt(Native.WindowFromPointFlags.CWP_SKIPINVISIBLE))
        If handle = IntPtr.Zero Then Return control.Visible
        Dim c As Control = Control.FromHandle(handle)

        If c IsNot Nothing AndAlso c IsNot control AndAlso c IsNot Me AndAlso c IsNot control.Parent Then
            Return False
        End If

        Return control.Visible
    End Function

    Private Function GetBackColor(ByVal control As Control) As Color
        Dim backColor As Color = control.BackColor

        If backColor.IsEmpty OrElse backColor = Color.Transparent Then
            backColor = SystemColors.Control
        ElseIf backColor.A < 255 Then
            backColor = Color.FromArgb(255, backColor)
        End If

        Return backColor
    End Function

    Protected Overrides Sub OnResize(ByVal e As EventArgs)
        UpdateRegion()
        MyBase.OnResize(e)
    End Sub

    Private Function GetControlRect(ByVal c As Control) As Rectangle
        If Not c.IsHandleCreated Then Return Rectangle.Empty
        Dim rect As Native.RECT
        Native.User32.GetWindowRect(c.Handle, rect)
        Dim p As Point = Me.PointToClient(rect.Location)
        Return New Rectangle(p, rect.Size)
    End Function

    Private Structure HighlightRegion
        Public Bounds As Rectangle
        Public BackColor As Color
        Public HighlightColor As eHighlightColor

        Public Sub New(ByVal bounds As Rectangle, ByVal backColor As Color, ByVal highlightColor As eHighlightColor)
            Me.Bounds = bounds
            Me.BackColor = backColor
            Me.HighlightColor = highlightColor
        End Sub
    End Structure

    Private _FocusHighlightControl As Control

    Public Property FocusHighlightControl As Control
        Get
            Return _FocusHighlightControl
        End Get
        Set(ByVal value As Control)
            _FocusHighlightControl = value
        End Set
    End Property

    Private _FocusHighlightColor As eHighlightColor = eHighlightColor.Blue

    Public Property FocusHighlightColor As eHighlightColor
        Get
            Return _FocusHighlightColor
        End Get
        Set(ByVal value As eHighlightColor)
            _FocusHighlightColor = value
        End Set
    End Property

    Private _CustomHighlightColors As Color() = Nothing

    Public Property CustomHighlightColors As Color()
        Get
            Return _CustomHighlightColors
        End Get
        Set(ByVal value As Color())
            _CustomHighlightColors = value
        End Set
    End Property
End Class
