Imports System.IO
Imports System.Globalization
Imports System.Collections.Generic

Public Class frmMain

    Private clickedRowIndex As Integer

    ' Undo and Redo stacks
    Private undoStack As New Stack(Of DataGridViewAction)()
    Private redoStack As New Stack(Of DataGridViewAction)()

    ' List to preserve original data for filtering
    Private originalRows As New List(Of Object())()

    ' Form constructor
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        ' Assign the context menu to the DataGridView
        InitializeContextMenu()

        ' Initialize the DataGridView properties and events
        InitializeDataGridView()

        ' Set DataGridView Anchor
        DataGridView1.Anchor = AnchorStyles.Top Or AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Bottom

        ' Handle CellEndEdit event
        AddHandler DataGridView1.CellEndEdit, AddressOf DataGridView1_CellEndEdit

        ' Set DateTimePicker properties
        DateTimePicker01.MaxDate = DateTime.Now.AddDays(1)

        ' Load data when the form loads
        AddHandler Me.Load, AddressOf frmMain_Load
    End Sub

    ' Event handler for DateTimePicker value changed
    Private Sub DateTimePicker01_ValueChanged(sender As Object, e As EventArgs) Handles DateTimePicker01.ValueChanged
        ' Extract the selected date from DateTimePicker01
        Dim selectedDate As DateTime = DateTimePicker01.Value
        Dim selectedMonth As Integer = selectedDate.Month
        Dim selectedYear As Integer = selectedDate.Year

        ' Filter the DataGridView based on the selected month and year
        FilterDataGrid(selectedMonth, selectedYear)
    End Sub

    ' Method to filter DataGridView rows based on selected month and year
    Private Sub FilterDataGrid(selectedMonth As Integer, selectedYear As Integer)
        DataGridView1.Rows.Clear()

        For Each rowValues As Object() In originalRows
            Dim dateValue As DateTime
            If DateTime.TryParse(rowValues(DataGridView1.Columns("dateColumn").Index)?.ToString(), dateValue) Then
                ' Check if the row's date matches the selected month and year
                If dateValue.Month = selectedMonth And dateValue.Year = selectedYear Then
                    ' Add the row values to the DataGridView
                    DataGridView1.Rows.Add(rowValues)
                End If
            End If
        Next

        ' Add totals row after filtering
        AddTotalsRow()
        UpdateTotals()
    End Sub

    ' Handle the DataGridView Mouse Down event to get the row index on right-click
    Private Sub DataGridView1_MouseDown(sender As Object, e As MouseEventArgs) Handles DataGridView1.MouseDown
        If e.Button = MouseButtons.Right Then
            Dim hitTestInfo As DataGridView.HitTestInfo = DataGridView1.HitTest(e.X, e.Y)
            If hitTestInfo.Type = DataGridViewHitTestType.Cell Then
                clickedRowIndex = hitTestInfo.RowIndex
                DataGridView1.ClearSelection()
                DataGridView1.Rows(clickedRowIndex).Selected = True
            End If
        End If
    End Sub

    ' Event handler for CellEndEdit to update Total Time
    Private Sub DataGridView1_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs)
        ' Ensure the edited cell is in either Start Time or Finish Time columns
        If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
            Dim cell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
            Dim oldValue = cell.Tag ' Retrieve the old value stored in Tag
            Dim newValue = cell.Value

            ' Record the cell edit action
            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.CellEdit,
                .RowIndex = e.RowIndex,
                .ColumnIndex = e.ColumnIndex,
                .OldValue = oldValue,
                .NewValue = newValue
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()

            Dim currentRow As DataGridViewRow = DataGridView1.Rows(e.RowIndex)
            If IsTotalsRow(currentRow) Then
                ' Do not process totals row
                Return
            End If

            If e.ColumnIndex = DataGridView1.Columns("startTimeColumn").Index OrElse e.ColumnIndex = DataGridView1.Columns("finishTimeColumn").Index Then
                Dim startTimeStr As String = currentRow.Cells("startTimeColumn").Value?.ToString()
                Dim finishTimeStr As String = currentRow.Cells("finishTimeColumn").Value?.ToString()
                Dim startTime As DateTime
                Dim finishTime As DateTime

                ' Try parsing Start Time and Finish Time cell values
                If DateTime.TryParseExact(startTimeStr, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, startTime) AndAlso
                   DateTime.TryParseExact(finishTimeStr, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, finishTime) Then
                    ' Calculate Total Time
                    Dim totalTime As TimeSpan = finishTime - startTime
                    If totalTime.TotalMinutes < 0 Then
                        ' If totalTime is negative, add 24 hours (handles times past midnight)
                        totalTime = totalTime.Add(TimeSpan.FromHours(24))
                    End If
                    ' Update Total Time cell value
                    Dim totalHours As Integer = CInt(Math.Floor(totalTime.TotalHours))
                    Dim totalMinutes As Integer = totalTime.Minutes
                    currentRow.Cells("totalTimeColumn").Value = $"{totalHours}:{totalMinutes:D2}"
                Else
                    ' If parsing fails, clear the Total Time cell
                    currentRow.Cells("totalTimeColumn").Value = ""
                End If
            End If

            ' Update totals and save data
            UpdateTotals()
            SaveData()
        End If
    End Sub

    ' Store the original value before editing
    Private Sub DataGridView1_CellBeginEdit(sender As Object, e As DataGridViewCellCancelEventArgs) Handles DataGridView1.CellBeginEdit
        If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
            Dim cell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
            cell.Tag = cell.Value ' Store the original value in the Tag property
        End If
    End Sub

    ' Assign the context menu to the DataGridView
    Private Sub InitializeContextMenu()
        ' Assign context menu to DataGridView
        DataGridView1.ContextMenuStrip = contextMenuMain

        ' Initially disable move up and move down menu items
        menuItemMoveUp.Enabled = False
        menuItemMoveDown.Enabled = False

        ' Initially disable Undo and Redo menu items
        menuItemUndo.Enabled = False
        menuItemRedo.Enabled = False
    End Sub

    ' Event handler for adding a row
    Private Sub menuItemAddRow_Click(sender As Object, e As EventArgs) Handles menuItemAddRow.Click
        AddRow()
        UpdateMoveMenuItems() ' Ensure move menu items are updated

        ' Record the add row action
        Dim action As New DataGridViewAction With {
            .Type = DataGridViewAction.ActionType.RowAdded,
            .RowIndex = DataGridView1.Rows.Count - 2 ' Index of the new row (before totals row)
        }
        undoStack.Push(action)
        redoStack.Clear() ' Clear redo stack when a new action is performed
        UpdateUndoRedoMenuItems()
    End Sub

    ' Event handler for deleting a row
    Private Sub menuItemDeleteRow_Click(sender As Object, e As EventArgs) Handles menuItemDeleteRow.Click
        If clickedRowIndex >= 0 AndAlso clickedRowIndex < DataGridView1.Rows.Count Then
            Dim selectedRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex)
            If IsTotalsRow(selectedRow) Then
                MessageBox.Show("The totals row cannot be deleted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                Return
            End If

            ' Record the delete row action
            Dim rowData As Object() = New Object(DataGridView1.Columns.Count - 1) {}
            For i As Integer = 0 To DataGridView1.Columns.Count - 1
                rowData(i) = selectedRow.Cells(i).Value
            Next

            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowDeleted,
                .RowIndex = clickedRowIndex,
                .RowData = rowData
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()

            DataGridView1.Rows.RemoveAt(clickedRowIndex)
            UpdateTotals() ' Update totals after deletion
            UpdateMoveMenuItems()
        End If
    End Sub

    ' Event handler for moving a row up
    Private Sub menuItemMoveUp_Click(sender As Object, e As EventArgs) Handles menuItemMoveUp.Click
        If clickedRowIndex > 0 AndAlso clickedRowIndex < DataGridView1.Rows.Count Then
            Dim selectedRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex)
            If IsTotalsRow(selectedRow) Then
                Return ' Do not move the totals row
            End If

            Dim targetRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex - 1)
            If IsTotalsRow(targetRow) Then
                Return ' Do not move a row above the totals row
            End If

            ' Record the move up action
            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowMoved,
                .SourceRowIndex = clickedRowIndex,
                .TargetRowIndex = clickedRowIndex - 1
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()

            DataGridView1.Rows.Remove(selectedRow)
            DataGridView1.Rows.Insert(clickedRowIndex - 1, selectedRow)
            DataGridView1.ClearSelection()
            DataGridView1.Rows(clickedRowIndex - 1).Selected = True
            clickedRowIndex -= 1
            UpdateMoveMenuItems()
        End If
    End Sub

    ' Event handler for moving a row down
    Private Sub menuItemMoveDown_Click(sender As Object, e As EventArgs) Handles menuItemMoveDown.Click
        If clickedRowIndex >= 0 AndAlso clickedRowIndex < DataGridView1.Rows.Count - 1 Then
            Dim selectedRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex)
            If IsTotalsRow(selectedRow) Then
                Return ' Do not move the totals row
            End If

            Dim targetRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex + 1)
            If IsTotalsRow(targetRow) Then
                Return ' Do not move a row below the totals row
            End If

            ' Record the move down action
            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowMoved,
                .SourceRowIndex = clickedRowIndex,
                .TargetRowIndex = clickedRowIndex + 1
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()

            DataGridView1.Rows.Remove(selectedRow)
            DataGridView1.Rows.Insert(clickedRowIndex + 1, selectedRow)
            DataGridView1.ClearSelection()
            DataGridView1.Rows(clickedRowIndex + 1).Selected = True
            clickedRowIndex += 1
            UpdateMoveMenuItems()
        End If
    End Sub

    ' Event handler for duplicating a row
    Private Sub menuItemDuplicateRow_Click(sender As Object, e As EventArgs) Handles menuItemDuplicateRow.Click
        If clickedRowIndex >= 0 AndAlso clickedRowIndex < DataGridView1.Rows.Count Then
            Dim selectedRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex)
            If IsTotalsRow(selectedRow) Then
                MessageBox.Show("The totals row cannot be duplicated.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                Return
            End If

            ' Duplicate the selected row
            Dim rowValues As Object() = New Object(selectedRow.Cells.Count - 1) {}
            For i As Integer = 0 To selectedRow.Cells.Count - 1
                rowValues(i) = selectedRow.Cells(i).Value
            Next

            DataGridView1.Rows.Insert(clickedRowIndex + 1, rowValues)
            UpdateTotals() ' Update totals after duplication
            UpdateMoveMenuItems() ' Update move menu items

            ' Record the duplicate row action
            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowDuplicated,
                .SourceRowIndex = clickedRowIndex,
                .TargetRowIndex = clickedRowIndex + 1
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()
        End If
    End Sub

    ' Method to update the status of move menu items
    Private Sub UpdateMoveMenuItems()
        If DataGridView1.CurrentCell IsNot Nothing Then
            Dim currentIndex As Integer = DataGridView1.CurrentCell.RowIndex
            Dim currentRow As DataGridViewRow = DataGridView1.Rows(currentIndex)

            If IsTotalsRow(currentRow) Then
                menuItemMoveUp.Enabled = False
                menuItemMoveDown.Enabled = False
            Else
                menuItemMoveUp.Enabled = (currentIndex > 0)
                menuItemMoveDown.Enabled = (currentIndex >= 0 AndAlso currentIndex < DataGridView1.Rows.Count - 2) ' Exclude totals row
            End If
        Else
            menuItemMoveUp.Enabled = False
            menuItemMoveDown.Enabled = False
        End If
        SaveData()
    End Sub

    ' Method to update Undo and Redo menu items
    Private Sub UpdateUndoRedoMenuItems()
        menuItemUndo.Enabled = undoStack.Count > 0
        menuItemRedo.Enabled = redoStack.Count > 0
    End Sub

    ' Event handler for the Undo menu item
    Private Sub menuItemUndo_Click(sender As Object, e As EventArgs) Handles menuItemUndo.Click
        If undoStack.Count > 0 Then
            Dim action As DataGridViewAction = undoStack.Pop()
            PerformUndo(action)
            redoStack.Push(action)
            UpdateUndoRedoMenuItems()
        End If
    End Sub

    ' Event handler for the Redo menu item
    Private Sub menuItemRedo_Click(sender As Object, e As EventArgs) Handles menuItemRedo.Click
        If redoStack.Count > 0 Then
            Dim action As DataGridViewAction = redoStack.Pop()
            PerformRedo(action)
            undoStack.Push(action)
            UpdateUndoRedoMenuItems()
        End If
    End Sub

    ' Perform the Undo action
    Private Sub PerformUndo(action As DataGridViewAction)
        Select Case action.Type
            Case DataGridViewAction.ActionType.CellEdit
                Dim cell = DataGridView1.Rows(action.RowIndex).Cells(action.ColumnIndex)
                cell.Value = action.OldValue
                UpdateTotals()

            Case DataGridViewAction.ActionType.RowAdded
                ' Remove the row that was added
                DataGridView1.Rows.RemoveAt(action.RowIndex)
                UpdateTotals()
                UpdateMoveMenuItems()

            Case DataGridViewAction.ActionType.RowDeleted
                ' Insert the deleted row back
                Dim newRow As DataGridViewRow = New DataGridViewRow()
                newRow.CreateCells(DataGridView1, action.RowData)
                DataGridView1.Rows.Insert(action.RowIndex, newRow)
                UpdateTotals()
                UpdateMoveMenuItems()

            Case DataGridViewAction.ActionType.RowMoved
                ' Move the row back to its original position
                Dim movedRow As DataGridViewRow = DataGridView1.Rows(action.TargetRowIndex)
                DataGridView1.Rows.Remove(movedRow)
                DataGridView1.Rows.Insert(action.SourceRowIndex, movedRow)
                UpdateMoveMenuItems()

            Case DataGridViewAction.ActionType.RowDuplicated
                ' Remove the duplicated row
                DataGridView1.Rows.RemoveAt(action.TargetRowIndex)
                UpdateTotals()
                UpdateMoveMenuItems()
        End Select
    End Sub

    ' Perform the Redo action
    Private Sub PerformRedo(action As DataGridViewAction)
        Select Case action.Type
            Case DataGridViewAction.ActionType.CellEdit
                Dim cell = DataGridView1.Rows(action.RowIndex).Cells(action.ColumnIndex)
                cell.Value = action.NewValue
                UpdateTotals()

            Case DataGridViewAction.ActionType.RowAdded
                ' Re-add the row that was added
                AddRowAtIndex(action.RowIndex)
                UpdateTotals()
                UpdateMoveMenuItems()

            Case DataGridViewAction.ActionType.RowDeleted
                ' Remove the row again
                DataGridView1.Rows.RemoveAt(action.RowIndex)
                UpdateTotals()
                UpdateMoveMenuItems()

            Case DataGridViewAction.ActionType.RowMoved
                ' Move the row again
                Dim movedRow As DataGridViewRow = DataGridView1.Rows(action.SourceRowIndex)
                DataGridView1.Rows.Remove(movedRow)
                DataGridView1.Rows.Insert(action.TargetRowIndex, movedRow)
                UpdateMoveMenuItems()

            Case DataGridViewAction.ActionType.RowDuplicated
                ' Duplicate the row again
                Dim sourceRow As DataGridViewRow = DataGridView1.Rows(action.SourceRowIndex)
                Dim rowValues As Object() = New Object(sourceRow.Cells.Count - 1) {}
                For i As Integer = 0 To sourceRow.Cells.Count - 1
                    rowValues(i) = sourceRow.Cells(i).Value
                Next
                DataGridView1.Rows.Insert(action.TargetRowIndex, rowValues)
                UpdateTotals()
                UpdateMoveMenuItems()
        End Select
    End Sub

    ' Helper method to add a row at a specific index
    Private Sub AddRowAtIndex(index As Integer)
        ' Create an empty row
        Dim name As String = " "
        Dim type As String = " "
        Dim dateValue As DateTime = DateTime.Now ' Use current date
        Dim startTime As DateTime = DateTime.Now
        Dim finishTime As DateTime = DateTime.Now.AddHours(1)
        Dim totalTime As TimeSpan = finishTime - startTime
        Dim amount As Decimal = 0
        Dim comment As String = " "

        ' Create a row with the data
        Dim totalHours As Integer = CInt(Math.Floor(totalTime.TotalHours))
        Dim totalMinutes As Integer = totalTime.Minutes
        Dim totalTimeStr As String = $"{totalHours}:{totalMinutes:D2}"

        ' Initialize an array with the correct number of elements
        Dim totalColumns As Integer = DataGridView1.Columns.Count
        Dim rowValues As Object() = New Object(totalColumns - 1) {}

        For i As Integer = 0 To totalColumns - 1
            Select Case DataGridView1.Columns(i).Name
                Case "nameColumn"
                    rowValues(i) = name
                Case "typeColumn"
                    rowValues(i) = type
                Case "dateColumn"
                    rowValues(i) = dateValue.ToString("d")
                Case "startTimeColumn"
                    rowValues(i) = startTime.ToString("HH:mm")
                Case "finishTimeColumn"
                    rowValues(i) = finishTime.ToString("HH:mm")
                Case "totalTimeColumn"
                    rowValues(i) = totalTimeStr
                Case "amountColumn"
                    rowValues(i) = "£" & amount.ToString("0.00")
                Case "commentColumn"
                    rowValues(i) = comment
                Case "totalsFlagColumn"
                    rowValues(i) = ""
                Case Else
                    rowValues(i) = ""
            End Select
        Next

        DataGridView1.Rows.Insert(index, rowValues)
    End Sub

    ' Event handler for DataGridView CellClick to update move menu items
    Private Sub DataGridView1_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellClick
        If e.RowIndex >= 0 AndAlso e.RowIndex < DataGridView1.Rows.Count Then
            UpdateMoveMenuItems()
        End If
    End Sub

    ' Method to initialize DataGridView properties and events
    Private Sub InitializeDataGridView()
        ' Assume columns are already added via the designer.

        ' If totalsFlagColumn is not added via designer, we can add it here
        If Not DataGridView1.Columns.Contains("totalsFlagColumn") Then
            ' Add a hidden column to identify the totals row
            Dim totalsFlagColumn As New DataGridViewTextBoxColumn()
            totalsFlagColumn.Name = "totalsFlagColumn"
            totalsFlagColumn.HeaderText = "IsTotalsRow"
            totalsFlagColumn.Visible = False ' Hide the column
            DataGridView1.Columns.Add(totalsFlagColumn)
        End If

        ' Set AutoSizeColumnsMode to Fill
        DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill

        ' Set the FillWeight to control the width of each column
        DataGridView1.Columns("nameColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("typeColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("dateColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("startTimeColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("finishTimeColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("totalTimeColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("amountColumn").FillWeight = 10 ' Adjust as needed
        DataGridView1.Columns("commentColumn").FillWeight = 30 ' Comment section should be the longest

        ' Make Total Time column read-only
        DataGridView1.Columns("totalTimeColumn").ReadOnly = True

        ' Disable automatic addition of new rows
        DataGridView1.AllowUserToAddRows = False

        ' Handle the CellValueChanged event
        AddHandler DataGridView1.CellValueChanged, AddressOf DataGridView1_CellValueChanged

        ' Disable automatic sorting for all columns
        For Each column As DataGridViewColumn In DataGridView1.Columns
            column.SortMode = DataGridViewColumnSortMode.Programmatic
        Next

        ' Handle the Sorted event
        AddHandler DataGridView1.Sorted, AddressOf DataGridView1_Sorted

        ' Handle the UserDeletingRow event
        AddHandler DataGridView1.UserDeletingRow, AddressOf DataGridView1_UserDeletingRow

        ' Handle the SelectionChanged event to deselect the totals row
        AddHandler DataGridView1.SelectionChanged, AddressOf DataGridView1_SelectionChanged

        ' Handle CellBeginEdit event to store the original value
        AddHandler DataGridView1.CellBeginEdit, AddressOf DataGridView1_CellBeginEdit
    End Sub

    ' Method to add a totals row to the DataGridView
    Private Sub AddTotalsRow()
        ' Remove existing totals row if present
        Dim existingTotalsRow As DataGridViewRow = GetTotalsRow()
        If existingTotalsRow IsNot Nothing Then
            DataGridView1.Rows.Remove(existingTotalsRow)
        End If

        ' Initialize an array with the correct number of elements
        Dim totalColumns As Integer = DataGridView1.Columns.Count
        Dim totalsRowValues As Object() = New Object(totalColumns - 1) {}

        ' Assign values based on column names
        For i As Integer = 0 To totalColumns - 1
            Select Case DataGridView1.Columns(i).Name
                Case "nameColumn"
                    totalsRowValues(i) = "Total"
                Case "totalTimeColumn"
                    totalsRowValues(i) = "0:00"
                Case "amountColumn"
                    totalsRowValues(i) = "£0.00"
                Case "totalsFlagColumn"
                    totalsRowValues(i) = "True"
                Case Else
                    totalsRowValues(i) = ""
            End Select
        Next

        DataGridView1.Rows.Add(totalsRowValues)

        Dim totalsRowView As DataGridViewRow = DataGridView1.Rows(DataGridView1.Rows.Count - 1)

        ' Set every cell in the totals row as read-only
        For Each cell As DataGridViewCell In totalsRowView.Cells
            cell.ReadOnly = True
        Next

        totalsRowView.DefaultCellStyle.BackColor = Color.LightGray ' Grey out the totals row
        totalsRowView.DefaultCellStyle.Font = New Font(DataGridView1.Font, FontStyle.Bold) ' Bold font for emphasis

        ' Set the totals row flag in the hidden column
        totalsRowView.Cells("totalsFlagColumn").Value = "True"
    End Sub

    ' Method to add a row to the DataGridView
    Private Sub AddRow()
        ' Example data to add
        Dim name As String = " "
        Dim type As String = " "
        Dim dateValue As DateTime = DateTime.Now ' Use current date
        Dim startTime As DateTime = DateTime.Now
        Dim finishTime As DateTime = DateTime.Now.AddHours(1)
        Dim totalTime As TimeSpan = finishTime - startTime
        Dim amount As Decimal = 0
        Dim comment As String = " "

        ' Create a row with the data
        Dim totalHours As Integer = CInt(Math.Floor(totalTime.TotalHours))
        Dim totalMinutes As Integer = totalTime.Minutes
        Dim totalTimeStr As String = $"{totalHours}:{totalMinutes:D2}"

        ' Initialize an array with the correct number of elements
        Dim totalColumns As Integer = DataGridView1.Columns.Count
        Dim rowValues As Object() = New Object(totalColumns - 1) {}

        For i As Integer = 0 To totalColumns - 1
            Select Case DataGridView1.Columns(i).Name
                Case "nameColumn"
                    rowValues(i) = name
                Case "typeColumn"
                    rowValues(i) = type
                Case "dateColumn"
                    rowValues(i) = dateValue.ToString("d")
                Case "startTimeColumn"
                    rowValues(i) = startTime.ToString("HH:mm")
                Case "finishTimeColumn"
                    rowValues(i) = finishTime.ToString("HH:mm")
                Case "totalTimeColumn"
                    rowValues(i) = totalTimeStr
                Case "amountColumn"
                    rowValues(i) = "£" & amount.ToString("0.00")
                Case "commentColumn"
                    rowValues(i) = comment
                Case "totalsFlagColumn"
                    rowValues(i) = ""
                Case Else
                    rowValues(i) = ""
            End Select
        Next

        ' Insert the row before the totals row
        Dim insertIndex As Integer = DataGridView1.Rows.Count - 1 ' Index before the totals row
        DataGridView1.Rows.Insert(insertIndex, rowValues)

        ' Update the originalRows list
        UpdateOriginalRows()

        ' Update totals
        UpdateTotals()
    End Sub

    ' Method to save data
    Private Sub SaveData()
        ' Save data to a CSV file
        Using writer As New StreamWriter("data.csv")
            ' Write header
            writer.WriteLine("Name,Type,Date,Start Time,Finish Time,Total Time,Amount,Comment")
            ' Write rows
            For Each row As DataGridViewRow In DataGridView1.Rows
                ' Skip the totals row
                If Not IsTotalsRow(row) Then
                    Dim name = If(row.Cells("nameColumn").Value, "").ToString()
                    Dim type = If(row.Cells("typeColumn").Value, "").ToString()

                    ' Format date as short date string
                    Dim dateValueObj = row.Cells("dateColumn").Value
                    Dim dateValue As String = ""
                    If dateValueObj IsNot Nothing Then
                        Dim dt As DateTime
                        If DateTime.TryParse(dateValueObj.ToString(), dt) Then
                            dateValue = dt.ToString("d")
                        Else
                            dateValue = dateValueObj.ToString()
                        End If
                    End If

                    Dim startTime = If(row.Cells("startTimeColumn").Value, "").ToString()
                    Dim finishTime = If(row.Cells("finishTimeColumn").Value, "").ToString()
                    Dim totalTime = If(row.Cells("totalTimeColumn").Value, "").ToString()
                    Dim amount = If(row.Cells("amountColumn").Value, "").ToString()
                    Dim comment = If(row.Cells("commentColumn").Value, "").ToString()

                    writer.WriteLine($"{name},{type},{dateValue},{startTime},{finishTime},{totalTime},{amount},{comment}")
                End If
            Next
        End Using

        ' Update the originalRows list
        UpdateOriginalRows()
    End Sub

    ' Method to load data
    Private Sub LoadData()
        ' Clear existing rows
        DataGridView1.Rows.Clear()

        ' Check if the file exists
        If File.Exists("data.csv") Then
            Using reader As New StreamReader("data.csv")
                ' Read header line
                Dim header As String = reader.ReadLine()

                ' Read rows
                While Not reader.EndOfStream
                    Dim line As String = reader.ReadLine()
                    Dim values As String() = line.Split(","c)
                    If values.Length < 8 Then
                        Continue While ' Skip invalid rows
                    End If

                    ' Initialize an array with the correct number of elements
                    Dim totalColumns As Integer = DataGridView1.Columns.Count
                    Dim extendedValues As Object() = New Object(totalColumns - 1) {}

                    ' Copy known values
                    For i As Integer = 0 To values.Length - 1
                        extendedValues(i) = values(i)
                    Next

                    ' Set empty values for the rest
                    For i As Integer = values.Length To totalColumns - 1
                        extendedValues(i) = ""
                    Next

                    ' Set totalsFlagColumn to empty string
                    If DataGridView1.Columns.Contains("totalsFlagColumn") Then
                        extendedValues(DataGridView1.Columns("totalsFlagColumn").Index) = ""
                    End If

                    DataGridView1.Rows.Add(extendedValues)
                End While
            End Using
        End If

        ' After loading data, add totals row and update totals
        AddTotalsRow()
        UpdateTotals()

        ' Populate the originalRows list with the loaded data
        UpdateOriginalRows()
    End Sub

    ' Event handler for cell value changes
    Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs)
        If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
            Dim currentRow As DataGridViewRow = DataGridView1.Rows(e.RowIndex)

            ' Make sure we're not on the totals row
            If IsTotalsRow(currentRow) Then
                Return
            End If

            ' This event is already handled in CellEndEdit
        End If
    End Sub

    ' Method to update totals in the totals row
    Private Sub UpdateTotals()
        Dim totalDuration As TimeSpan = TimeSpan.Zero
        Dim totalAmount As Decimal = 0D

        For Each row As DataGridViewRow In DataGridView1.Rows
            If IsTotalsRow(row) Then
                Continue For
            End If

            ' Calculate total time
            Dim totalTimeStr As String = row.Cells("totalTimeColumn").Value?.ToString()
            Dim totalTime As TimeSpan
            If TimeSpan.TryParseExact(totalTimeStr, "%h\:mm", CultureInfo.InvariantCulture, TimeSpanStyles.None, totalTime) Then
                totalDuration += totalTime
            End If

            ' Calculate total amount
            Dim amountStr As String = row.Cells("amountColumn").Value?.ToString().Replace("£", "")
            Dim amount As Decimal
            If Decimal.TryParse(amountStr, amount) Then
                totalAmount += amount
            End If
        Next

        ' Update totals row
        Dim totalsRow As DataGridViewRow = GetTotalsRow()
        If totalsRow IsNot Nothing Then
            Dim totalHours As Integer = CInt(Math.Floor(totalDuration.TotalHours))
            Dim totalMinutes As Integer = totalDuration.Minutes

            ' Display total hours and minutes, including hours over 24
            totalsRow.Cells("totalTimeColumn").Value = $"{totalHours}:{totalMinutes:D2}"
            totalsRow.Cells("amountColumn").Value = "£" & totalAmount.ToString("0.00")
        End If
    End Sub

    ' Event handler for Sorted event
    Private Sub DataGridView1_Sorted(sender As Object, e As EventArgs)
        ' Move totals row to the bottom
        Dim totalsRow As DataGridViewRow = GetTotalsRow()
        If totalsRow IsNot Nothing Then
            DataGridView1.Rows.Remove(totalsRow)
            DataGridView1.Rows.Add(totalsRow)
        End If
    End Sub

    ' Event handler for UserDeletingRow to prevent deletion of the totals row
    Private Sub DataGridView1_UserDeletingRow(sender As Object, e As DataGridViewRowCancelEventArgs)
        If IsTotalsRow(e.Row) Then
            MessageBox.Show("The totals row cannot be deleted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            e.Cancel = True ' Cancel the deletion
        Else
            ' Record the delete action for Undo
            Dim rowData As Object() = New Object(DataGridView1.Columns.Count - 1) {}
            For i As Integer = 0 To DataGridView1.Columns.Count - 1
                rowData(i) = e.Row.Cells(i).Value
            Next

            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowDeleted,
                .RowIndex = e.Row.Index,
                .RowData = rowData
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack
            UpdateUndoRedoMenuItems()
        End If
    End Sub

    ' Event handler for SelectionChanged to deselect the totals row
    Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) Handles DataGridView1.SelectionChanged
        If DataGridView1.CurrentRow IsNot Nothing AndAlso IsTotalsRow(DataGridView1.CurrentRow) Then
            ' Deselect the totals row
            DataGridView1.CurrentCell = Nothing
        End If
    End Sub

    ' Helper method to check if a row is the totals row
    Private Function IsTotalsRow(row As DataGridViewRow) As Boolean
        If DataGridView1.Columns.Contains("totalsFlagColumn") Then
            Dim totalsFlagColumnIndex As Integer = DataGridView1.Columns("totalsFlagColumn").Index
            Return row.Cells(totalsFlagColumnIndex).Value IsNot Nothing AndAlso row.Cells(totalsFlagColumnIndex).Value.ToString() = "True"
        Else
            Return False
        End If
    End Function

    ' Helper method to get the totals row
    Private Function GetTotalsRow() As DataGridViewRow
        For Each row As DataGridViewRow In DataGridView1.Rows
            If IsTotalsRow(row) Then
                Return row
            End If
        Next
        Return Nothing
    End Function

    ' Method to update the originalRows list
    Private Sub UpdateOriginalRows()
        originalRows.Clear()
        For Each row As DataGridViewRow In DataGridView1.Rows
            If Not IsTotalsRow(row) Then
                ' Collect the cell values
                Dim rowValues As Object() = New Object(DataGridView1.Columns.Count - 1) {}
                For i As Integer = 0 To DataGridView1.Columns.Count - 1
                    rowValues(i) = row.Cells(i).Value
                Next
                originalRows.Add(rowValues)
            End If
        Next
    End Sub

    ' Event handler for form load
    Private Sub frmMain_Load(sender As Object, e As EventArgs)
        LoadData()
        UpdateUndoRedoMenuItems()

        ' Set the DateTimePicker to the current date
        DateTimePicker01.Value = DateTime.Now
    End Sub

    ' Event handler for resetting the DateTimePicker to current date
    Private Sub CTMReset_Click(sender As Object, e As EventArgs) Handles CTMReset.Click
        DateTimePicker01.Value = DateTime.Now
    End Sub

End Class

' Class to represent actions for undo and redo
Public Class DataGridViewAction
    Public Enum ActionType
        CellEdit
        RowAdded
        RowDeleted
        RowMoved
        RowDuplicated
    End Enum

    Public Property Type As ActionType

    ' For cell edits
    Public Property RowIndex As Integer
    Public Property ColumnIndex As Integer
    Public Property OldValue As Object
    Public Property NewValue As Object

    ' For row addition and deletion
    Public Property RowData As Object() ' Required for RowDeleted actions

    ' For row moves
    Public Property SourceRowIndex As Integer
    Public Property TargetRowIndex As Integer

    ' For row duplication
    ' .SourceRowIndex is the index of the original row
    ' .TargetRowIndex is the index where the duplicated row is inserted
End Class


System.ArgumentException HResult=0x80070057 Message=Column named totalsFlagColumn cannot be found. Arg_ParamName_Name Source=System.Windows.Forms StackTrace: at System.Windows.Forms.DataGridViewCellCollection.get_Item(String columnName) at Dogs_And_Tails.frmMain.AddTotalsRow() in D:\01 - Grags\Visual Studio\Dogs And Tails\Dogs And Tails\frmMain.vb:line 578 at Dogs_And_Tails.frmMain.FilterDataGrid(Int32 selectedMonth, Int32 selectedYear) in D:\01 - Grags\Visual Studio\Dogs And Tails\Dogs And Tails\frmMain.vb:line 69 at Dogs_And_Tails.frmMain.DateTimePicker01_ValueChanged(Object sender, EventArgs e) in D:\01 - Grags\Visual Studio\Dogs And Tails\Dogs And Tails\frmMain.vb:line 50 at System.Windows.Forms.DateTimePicker.OnValueChanged(EventArgs eventargs) at System.Windows.Forms.DateTimePicker.set_Value(DateTime value) at Dogs_And_Tails.frmMain.InitializeComponent() in D:\01 - Grags\Visual Studio\Dogs And Tails\Dogs And Tails\frmMain.Designer.vb:line 210 at Dogs_And_Tails.frmMain..ctor() in D:\01 - Grags\Visual Studio\Dogs And Tails\Dogs And Tails\frmMain.vb:line 19 at System.RuntimeType.CreateInstanceOfT()