Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Diagnostics
Imports System.Windows.Forms
Imports System.Text
Imports System.Drawing

Public Class Frm_taskbar

    Private Const WM_GETICON As Integer = &H7F
    Private Const ICON_SMALL As Integer = 0
    Private Const ICON_BIG As Integer = 1
    Private Const SW_RESTORE As Integer = 9
    Private Const SW_MINIMIZE As Integer = 6
    Private Const SW_MAXIMIZE As Integer = 3
    Private Const WM_CLOSE As Integer = &H10
    Private Const SWP_SHOWWINDOW As UInteger = &H40
    Private Const GCL_HICON As Integer = -14
    ' newly added constants
    Private Const SWP_NOZORDER As UInteger = &H4
    Private Const SWP_NOACTIVATE As UInteger = &H10
    Private Const SW_SHOW As Integer = 5

    Private FrmO_imageList As New ImageList()
    Private FrmV_windowHandles As New Dictionary(Of String, IntPtr)()
    Private FrmO_savedWindows As New Dictionary(Of String, WindowStateInfo)()

    Private FrmV_layoutFilePath As String = Path.Combine(Application.StartupPath, "layout.txt")
    Private WithEvents AutoRefreshTimer As New Timer()
    Private FrmO_contextMenu As New ContextMenuStrip()
    Dim FrmV_TimerCounterIs As Integer

#Region "Structures"

    <StructLayout(LayoutKind.Sequential)>
    Private Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
    End Structure

    Private Structure WindowStateInfo
        Public Rect As RECT
        Public WindowState As Integer
    End Structure

#End Region

#Region "Win32 Imports"

    Private Delegate Function EnumWindowsProc(hWnd As IntPtr, lParam As IntPtr) As Boolean

    <DllImport("user32.dll")>
    Private Shared Function EnumWindows(lpEnumFunc As EnumWindowsProc, lParam As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function GetWindowText(hWnd As IntPtr, lpString As StringBuilder, nMaxCount As Integer) As Integer
    End Function

    <DllImport("user32.dll")>
    Private Shared Function IsWindowVisible(hWnd As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll")>
    Private Shared Function ShowWindow(hWnd As IntPtr, nCmdShow As Integer) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function SetForegroundWindow(hWnd As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function PostMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function GetWindowThreadProcessId(hWnd As IntPtr, ByRef processId As Integer) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function GetWindowRect(hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function IsIconic(hWnd As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function IsZoomed(hWnd As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SetWindowPos(hWnd As IntPtr, hWndInsertAfter As IntPtr,
                                         X As Integer, Y As Integer, cx As Integer, cy As Integer,
                                         uFlags As UInteger) As Boolean
    End Function

    <DllImport("user32.dll", EntryPoint:="GetClassLong", SetLastError:=True)>
    Private Shared Function GetClassLong32(hWnd As IntPtr, nIndex As Integer) As Integer
    End Function

    <DllImport("user32.dll", EntryPoint:="GetClassLongPtrW", SetLastError:=True)>
    Private Shared Function GetClassLongPtr64(hWnd As IntPtr, nIndex As Integer) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function IsWindow(hWnd As IntPtr) As Boolean
    End Function

#End Region

#Region "Helper Functions"

    Private Function GetClassLongPtr(hWnd As IntPtr, nIndex As Integer) As IntPtr
        If IntPtr.Size = 8 Then
            Return GetClassLongPtr64(hWnd, nIndex)
        Else
            Return New IntPtr(GetClassLong32(hWnd, nIndex))
        End If
    End Function

    Private Function GetWindowIcon(hWnd As IntPtr) As Icon
        Dim hIcon As IntPtr = SendMessage(hWnd, WM_GETICON, New IntPtr(ICON_BIG), IntPtr.Zero)
        If hIcon = IntPtr.Zero Then
            hIcon = GetClassLongPtr(hWnd, GCL_HICON)
        End If
        If hIcon <> IntPtr.Zero Then
            Return Icon.FromHandle(hIcon)
        End If

        Dim pid As Integer
        GetWindowThreadProcessId(hWnd, pid)
        Try
            Dim proc As Process = Process.GetProcessById(pid)
            If Not String.IsNullOrEmpty(proc.MainModule.FileName) Then
                Return Icon.ExtractAssociatedIcon(proc.MainModule.FileName)
            End If
        Catch
        End Try
        Return SystemIcons.Application
    End Function

    ' newly added method
    Private Function IsRectWithinMonitors(rect As RECT) As Boolean
        Dim rectBounds As New Rectangle(rect.Left, rect.Top,
                                        rect.Right - rect.Left,
                                        rect.Bottom - rect.Top)

        For Each screen As Screen In Screen.AllScreens
            If screen.WorkingArea.IntersectsWith(rectBounds) Then
                Return True
            End If
        Next

        Return False
    End Function

    ' newly added method
    Private Function AdjustRectToMonitors(rect As RECT) As RECT
        Dim width As Integer = rect.Right - rect.Left
        Dim height As Integer = rect.Bottom - rect.Top
        Dim centerX As Integer = rect.Left + (width \ 2)
        Dim centerY As Integer = rect.Top + (height \ 2)


        Dim closestScreen As Screen = Nothing
        Dim minDistance As Double = Double.MaxValue

        For Each screen As Screen In Screen.AllScreens
            Dim screenCenterX As Integer = screen.WorkingArea.Left + (screen.WorkingArea.Width \ 2)
            Dim screenCenterY As Integer = screen.WorkingArea.Top + (screen.WorkingArea.Height \ 2)
            Dim distance As Double = Math.Sqrt(Math.Pow(centerX - screenCenterX, 2) + Math.Pow(centerY - screenCenterY, 2))

            If distance < minDistance Then
                minDistance = distance
                closestScreen = screen
            End If
        Next


        If closestScreen Is Nothing Then
            closestScreen = Screen.PrimaryScreen
        End If


        Dim adjustedRect As RECT = rect
        Dim workArea As Rectangle = closestScreen.WorkingArea


        If width > workArea.Width Then
            width = workArea.Width
        End If
        If height > workArea.Height Then
            height = workArea.Height
        End If


        If rect.Left < workArea.Left Then
            adjustedRect.Left = workArea.Left
        ElseIf rect.Left + width > workArea.Right Then
            adjustedRect.Left = workArea.Right - width
        End If


        If rect.Top < workArea.Top Then
            adjustedRect.Top = workArea.Top
        ElseIf rect.Top + height > workArea.Bottom Then
            adjustedRect.Top = workArea.Bottom - height
        End If

        adjustedRect.Right = adjustedRect.Left + width
        adjustedRect.Bottom = adjustedRect.Top + height

        Return adjustedRect
    End Function

#End Region

#Region "Main Logic"

    Private Sub UpdateTaskbarContents()
        Lstview_TaskbarContents.Items.Clear()
        FrmV_windowHandles.Clear()
        FrmO_imageList.Images.Clear()
        FrmO_imageList.ImageSize = New Size(16, 16)
        Lstview_TaskbarContents.SmallImageList = FrmO_imageList
        EnumWindows(AddressOf EnumWindowsCallback, IntPtr.Zero)
    End Sub

    Private Function EnumWindowsCallback(hWnd As IntPtr, lParam As IntPtr) As Boolean
        If IsWindowVisible(hWnd) Then
            Dim title As New StringBuilder(256)
            GetWindowText(hWnd, title, title.Capacity)
            Dim windowTitle As String = title.ToString().Trim()

            If Not String.IsNullOrEmpty(windowTitle) Then
                Dim pid As Integer
                GetWindowThreadProcessId(hWnd, pid)

                Dim procName As String = ""
                Try
                    Dim proc As Process = Process.GetProcessById(pid)
                    procName = Path.GetFileNameWithoutExtension(proc.ProcessName)
                Catch
                    procName = "Unknown"
                End Try

                Dim key As String = $"{procName}::{windowTitle}"


                Dim originalKey As String = key
                Dim counter As Integer = 1
                While FrmV_windowHandles.ContainsKey(key)
                    counter += 1
                    key = $"{originalKey} #{counter}"
                End While

                Dim appGroup As ListViewGroup = Nothing
                For Each grp As ListViewGroup In Lstview_TaskbarContents.Groups
                    If grp.Name = procName Then
                        appGroup = grp
                        Exit For
                    End If
                Next

                If appGroup Is Nothing Then
                    appGroup = New ListViewGroup(procName, HorizontalAlignment.Left)
                    appGroup.Name = procName
                    Lstview_TaskbarContents.Groups.Add(appGroup)
                End If

                Dim icon As Icon = GetWindowIcon(hWnd)
                FrmO_imageList.Images.Add(key, icon)

                Dim item As New ListViewItem(windowTitle, key)
                item.Group = appGroup
                Lstview_TaskbarContents.Items.Add(item)

                FrmV_windowHandles(key) = hWnd
            End If
        End If
        Return True
    End Function

#End Region

#Region "Button Actions"

    'updated code
    Private Sub ActivateWindow(hWnd As IntPtr)
        If IsIconic(hWnd) Then
            ShowWindow(hWnd, SW_RESTORE)
        ElseIf IsZoomed(hWnd) Then
            ShowWindow(hWnd, SW_MAXIMIZE)
        Else
            ShowWindow(hWnd, SW_SHOW)
        End If
        SetForegroundWindow(hWnd)
    End Sub

    Private Sub Lstview_TaskbarContents_DoubleClick(sender As Object, e As EventArgs) Handles Lstview_TaskbarContents.DoubleClick
        If Lstview_TaskbarContents.SelectedItems.Count = 0 Then Return

        Dim selectedKey As String = Lstview_TaskbarContents.SelectedItems(0).ImageKey
        If FrmV_windowHandles.ContainsKey(selectedKey) Then
            ActivateWindow(FrmV_windowHandles(selectedKey))
        End If
    End Sub

    'updated method
    Private Sub Btn_SaveLayout_Click(sender As Object, e As EventArgs) Handles Btn_SaveLayout.Click
        Label1.Text = "Processing ..."
        Application.DoEvents()

        FrmO_savedWindows.Clear()

        Try
            FileOpen(1, FrmV_layoutFilePath, OpenMode.Output)

            For Each kvp In FrmV_windowHandles
                Dim hWnd As IntPtr = kvp.Value


                If Not IsWindow(hWnd) Then
                    Continue For
                End If

                Dim rect As RECT
                If GetWindowRect(hWnd, rect) Then
                    Dim state As Integer
                    If IsIconic(hWnd) Then
                        state = SW_MINIMIZE
                    ElseIf IsZoomed(hWnd) Then
                        state = SW_MAXIMIZE
                    Else
                        state = SW_RESTORE
                    End If

                    FrmO_savedWindows(kvp.Key) = New WindowStateInfo With {.Rect = rect, .WindowState = state}


                    PrintLine(1, kvp.Key & "|" & rect.Left & "|" & rect.Top & "|" & rect.Right & "|" & rect.Bottom & "|" & state)
                End If
            Next

            FileClose(1)

            FrmV_TimerCounterIs = 0
            Label1.Text = "Saved Ok"
            Timer1.Enabled = True
        Catch ex As Exception
            FileClose(1)
            MessageBox.Show("Error saving layout: " & ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Label1.Text = "Save Failed"
        End Try
    End Sub

    'updated method
    Private Sub Btn_RestoreLayout_Click(sender As Object, e As EventArgs) Handles Btn_RestoreLayout.Click
        If Not File.Exists(FrmV_layoutFilePath) Then
            MessageBox.Show("No saved layout file found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        Label1.Text = "Processing ..."
        Application.DoEvents()

        Dim restoredCount As Integer = 0
        Dim notFoundCount As Integer = 0
        Dim errorList As New List(Of String)()

        Try
            Using reader As New StreamReader(FrmV_layoutFilePath, Encoding.UTF8)
                While Not reader.EndOfStream
                    Dim line As String = reader.ReadLine()
                    If String.IsNullOrWhiteSpace(line) Then Continue While

                    Dim parts() As String = line.Split("|"c)
                    If parts.Length < 6 Then Continue While

                    Dim windowKey As String = parts(0)

                    Try
                        Dim rect As RECT
                        rect.Left = CInt(parts(1))
                        rect.Top = CInt(parts(2))
                        rect.Right = CInt(parts(3))
                        rect.Bottom = CInt(parts(4))
                        Dim state As Integer = CInt(parts(5))


                        If FrmV_windowHandles.ContainsKey(windowKey) Then
                            Dim hWnd As IntPtr = FrmV_windowHandles(windowKey)

                            If IsWindow(hWnd) Then

                                If Not IsRectWithinMonitors(rect) Then
                                    rect = AdjustRectToMonitors(rect)
                                End If

                                Select Case state
                                    Case SW_MINIMIZE
                                        ShowWindow(hWnd, SW_MINIMIZE)
                                    Case SW_MAXIMIZE
                                        ShowWindow(hWnd, SW_RESTORE)
                                        SetWindowPos(hWnd, IntPtr.Zero, rect.Left, rect.Top,
                                                     rect.Right - rect.Left, rect.Bottom - rect.Top,
                                                     SWP_NOZORDER Or SWP_NOACTIVATE)
                                        ShowWindow(hWnd, SW_MAXIMIZE)
                                    Case Else
                                        ShowWindow(hWnd, SW_RESTORE)
                                        SetWindowPos(hWnd, IntPtr.Zero, rect.Left, rect.Top,
                                                     rect.Right - rect.Left, rect.Bottom - rect.Top,
                                                     SWP_NOZORDER Or SWP_NOACTIVATE)
                                End Select

                                restoredCount += 1
                            Else
                                errorList.Add($"{windowKey} (handle invalid)")
                            End If
                        Else

                            notFoundCount += 1
                            errorList.Add($"{windowKey} (not found)")
                        End If
                    Catch ex As Exception
                        errorList.Add($"{windowKey} ({ex.Message})")
                    End Try
                End While
            End Using

            FrmV_TimerCounterIs = 0

            If restoredCount > 0 Then
                Label1.Text = $"Restored {restoredCount} windows"

                If notFoundCount > 0 OrElse errorList.Count > restoredCount Then
                    Dim msg As String = $"Successfully restored {restoredCount} window(s).{vbCrLf}"
                    If notFoundCount > 0 Then
                        msg &= $"{vbCrLf}{notFoundCount} window(s) not found (may be closed)."
                    End If
                    If errorList.Count > 0 Then
                        msg &= $"{vbCrLf}{vbCrLf}Issues:{vbCrLf}"
                        For i As Integer = 0 To Math.Min(errorList.Count - 1, 9)
                            msg &= $"  • {errorList(i)}{vbCrLf}"
                        Next
                        If errorList.Count > 10 Then
                            msg &= $"  ... and {errorList.Count - 10} more"
                        End If
                    End If
                    MessageBox.Show(msg, "Layout Restore Complete", MessageBoxButtons.OK, MessageBoxIcon.Information)
                End If
            Else
                Label1.Text = "No windows restored"
                MessageBox.Show("No matching windows found to restore. Make sure the applications are running.",
                                "Restore Failed", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            End If

            Timer1.Enabled = True

        Catch ex As Exception
            MessageBox.Show($"Error restoring layout: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Label1.Text = "Restore Failed"
        End Try
    End Sub

#End Region

#Region "Context Menu Actions"

    'updated method
    Private Sub ContextMenu_Activate_Click(sender As Object, e As EventArgs)
        If Lstview_TaskbarContents.SelectedItems.Count = 0 Then Return

        Dim selectedKey As String = Lstview_TaskbarContents.SelectedItems(0).ImageKey
        If FrmV_windowHandles.ContainsKey(selectedKey) Then
            ActivateWindow(FrmV_windowHandles(selectedKey))
        End If
    End Sub

    Private Sub ContextMenu_Close_Click(sender As Object, e As EventArgs)
        If Lstview_TaskbarContents.SelectedItems.Count = 0 Then Return

        Dim selectedKey As String = Lstview_TaskbarContents.SelectedItems(0).ImageKey
        If String.IsNullOrEmpty(selectedKey) Then
            selectedKey = Lstview_TaskbarContents.SelectedItems(0).Text
        End If

        If FrmV_windowHandles.ContainsKey(selectedKey) Then
            Dim hWnd As IntPtr = FrmV_windowHandles(selectedKey)

            If hWnd = Me.Handle Then
                MessageBox.Show("You cannot close Taskbar Monitor from here.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information)
                Return
            End If

            If Not IsWindow(hWnd) Then
                MessageBox.Show("Window handle is no longer valid.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Return
            End If

            SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero)
        End If
    End Sub

    Private Const KEYEVENTF_EXTENDEDKEY As UInteger = &H1
    Private Const KEYEVENTF_KEYUP As UInteger = &H2
    Private Const VK_LWIN As Byte = &H5B
    Private Const VK_S As Byte = &H53

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Sub keybd_event(bVk As Byte, bScan As Byte, dwFlags As UInteger, dwExtraInfo As UIntPtr)
    End Sub

    Private Sub ContextMenu_Start_Click(sender As Object, e As EventArgs)
        Try
            keybd_event(VK_LWIN, 0, KEYEVENTF_EXTENDEDKEY, UIntPtr.Zero)
            keybd_event(VK_LWIN, 0, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, UIntPtr.Zero)
        Catch ex As Exception
            MessageBox.Show("Unable to open Start menu." & vbCrLf & ex.Message)
        End Try
    End Sub

    Private Sub ContextMenu_Search_Click(sender As Object, e As EventArgs)
        Try
            keybd_event(VK_LWIN, 0, KEYEVENTF_EXTENDEDKEY, UIntPtr.Zero)
            keybd_event(VK_S, 0, KEYEVENTF_EXTENDEDKEY, UIntPtr.Zero)
            keybd_event(VK_S, 0, KEYEVENTF_KEYUP, UIntPtr.Zero)
            keybd_event(VK_LWIN, 0, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, UIntPtr.Zero)
        Catch ex As Exception
            MessageBox.Show("Unable to open Search." & vbCrLf & ex.Message)
        End Try
    End Sub

#End Region

#Region "Form Load and Timers"

    Private Sub Frm_taskbar_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Lstview_TaskbarContents.Columns.Add("Window", 350)
        Lstview_TaskbarContents.View = View.Details
        Lstview_TaskbarContents.SmallImageList = FrmO_imageList

        FrmO_contextMenu.Items.Clear()
        FrmO_contextMenu.Items.Add("Activate", Nothing, AddressOf ContextMenu_Activate_Click)
        FrmO_contextMenu.Items.Add("Close", Nothing, AddressOf ContextMenu_Close_Click)
        FrmO_contextMenu.Items.Add(New ToolStripSeparator())
        FrmO_contextMenu.Items.Add("Start Menu", Nothing, AddressOf ContextMenu_Start_Click)
        FrmO_contextMenu.Items.Add("Search", Nothing, AddressOf ContextMenu_Search_Click)
        Lstview_TaskbarContents.ContextMenuStrip = FrmO_contextMenu

        AutoRefreshTimer.Interval = 30000
        AutoRefreshTimer.Start()

        UpdateTaskbarContents()
        Me.Height = Screen.PrimaryScreen.WorkingArea.Height
        Me.Top = 0
        Me.Left = 0
    End Sub

    Private Sub AutoRefreshTimer_Tick(sender As Object, e As EventArgs) Handles AutoRefreshTimer.Tick
        UpdateTaskbarContents()
    End Sub


    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        FrmV_TimerCounterIs += 1
        If Label1.Visible = True Then
            Label1.Visible = False
        Else
            Label1.Visible = True
        End If
        If FrmV_TimerCounterIs >= 10 Then
            FrmV_TimerCounterIs = 0
            Timer1.Enabled = False
            Label1.Text = "Waiting..."
            Label1.Visible = True
        End If
    End Sub

    Private Sub Txt_DateTime_TextChanged(sender As Object, e As EventArgs) Handles Txt_DateTime.TextChanged
    End Sub

    Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
        Dim DaysNameIs As String = ".Sunday.Monday.Tuesday.Wednesday.Thursday.Friday.Saturday"
        Dim S = Split(DaysNameIs, ".")
        Dim TodayNumberIs As Integer
        Dim TodayNameIs As String

        TodayNumberIs = Weekday(Now)
        TodayNameIs = S(TodayNumberIs)

        Txt_DateTime.Text = TimeOfDay
        Txt_DateTime.Text = Txt_DateTime.Text & vbCrLf & Date.Today.ToString("yyyy-MM-dd")
        Txt_DateTime.Text = Txt_DateTime.Text & vbCrLf & TodayNameIs
    End Sub

    Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
        If CheckBox1.Checked = True Then
            Timer3.Enabled = True
        Else
            Timer3.Enabled = False
        End If
    End Sub

    Private Sub Timer3_Tick(sender As Object, e As EventArgs) Handles Timer3.Tick
        Btn_SaveLayout_Click(sender, e)
    End Sub

#End Region

End Class