Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Windows.Forms
Imports System.Collections.Generic

Public Class RoomView
    ' Constants for grid dimensions and indentation
    Const INDENT As Integer = 20
    Const SQCOUNTX As Integer = 15
    Const SQCOUNTY As Integer = 11
    Const WALL_THICKNESS As Integer = 10

    ' Class-level variables
    Dim WallX(SQCOUNTX, SQCOUNTY) As Boolean ' Vertical walls, including edges
    Dim WallY(SQCOUNTX, SQCOUNTY) As Boolean ' Horizontal walls, including edges
    Dim SQSizeX As Integer
    Dim SQSizeY As Integer
    Dim highlightedX As Integer
    Dim highlightedY As Integer
    Dim highlightedWallX As Boolean = False
    Dim highlightedWallY As Boolean = False

    ' Variables for mouse state
    Dim isRightMouseDown As Boolean = False
    Dim isLeftMouseDown As Boolean = False
    Dim isMouseInsideGrid As Boolean = False

    ' Variable to store the current file name
    Private currentFilename As String = ""

    ' Variables for the drawing styles
    Private wallsHatchStyle As HatchStyle = HatchStyle.Cross ' Default HatchStyle for walls
    Private wallsColor1 As Color = Color.Black ' Default Color1 for walls
    Private wallsColor2 As Color = Color.White ' Default Color2 for walls

    Private floorsHatchStyle As HatchStyle = HatchStyle.Cross ' Default HatchStyle for floors
    Private floorsColor1 As Color = Color.Black ' Default Color1 for floors
    Private floorsColor2 As Color = Color.White ' Default Color2 for floors

    ' Variable for the highlight color
    Private highlightColor As Color = Color.Black

    ' Variables for the highlight timer
    Private highlightTimer As Timer

    ' Variable for Premades folder path
    Private premadesFolderPath As String = "D:\01 - Grags\Visual Studio\Enigma\Data\Premades"

    ' Undo/Redo functionality variables
    Private undoStack As New Stack(Of ActionSnapshot)
    Private redoStack As New Stack(Of ActionSnapshot)
    Private actionInProgress As Boolean = False
    Private actionState As ActionSnapshot

    ' Flag to prevent programmatic changes from triggering events
    Private isProgrammaticChange As Boolean = False

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

        ' Initialize walls
        For i As Integer = 0 To SQCOUNTX
            For j As Integer = 0 To SQCOUNTY
                WallX(i, j) = True
                WallY(i, j) = True
            Next
        Next

        ' Initialize the highlight timer
        highlightTimer = New Timer()
        highlightTimer.Interval = 500 ' Flash every 500 milliseconds
        AddHandler highlightTimer.Tick, AddressOf HighlightTimer_Tick
        highlightTimer.Start()

        ' Populate the lstHatchstyles ListBox with HatchStyle names
        For Each hsName In [Enum].GetNames(GetType(HatchStyle))
            lstHatchstyles.Items.Add(hsName)
        Next

        ' Select the default hatch style in the ListBox
        lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
    End Sub

    ' ===========================================
    ' Undo/Redo Functionality
    ' ===========================================

    ' Class to store the action state (walls, styles, and colors)
    Private Class ActionSnapshot
        Public WallX(,) As Boolean
        Public WallY(,) As Boolean
        Public wallsHatchStyle As HatchStyle
        Public wallsColor1 As Color
        Public wallsColor2 As Color
        Public floorsHatchStyle As HatchStyle
        Public floorsColor1 As Color
        Public floorsColor2 As Color

        ' Constructor to create a deep copy of the walls and styles
        Public Sub New(wallX As Boolean(,), wallY As Boolean(,),
                       wallsHatchStyle As HatchStyle, wallsColor1 As Color, wallsColor2 As Color,
                       floorsHatchStyle As HatchStyle, floorsColor1 As Color, floorsColor2 As Color)
            Me.WallX = CType(wallX.Clone(), Boolean(,))
            Me.WallY = CType(wallY.Clone(), Boolean(,))
            Me.wallsHatchStyle = wallsHatchStyle
            Me.wallsColor1 = wallsColor1
            Me.wallsColor2 = wallsColor2
            Me.floorsHatchStyle = floorsHatchStyle
            Me.floorsColor1 = floorsColor1
            Me.floorsColor2 = floorsColor2
        End Sub
    End Class

    ' Method to begin an action (save state for undo)
    Private Sub BeginAction()
        If Not actionInProgress Then
            ' Save the current state
            actionState = New ActionSnapshot(WallX, WallY,
                                             wallsHatchStyle, wallsColor1, wallsColor2,
                                             floorsHatchStyle, floorsColor1, floorsColor2)
            actionInProgress = True
        End If
    End Sub

    ' Method to end an action (push state onto undo stack)
    Private Sub EndAction()
        If actionInProgress Then
            ' Push the saved state onto undo stack
            undoStack.Push(actionState)
            ' Clear the redo stack
            redoStack.Clear()
            ' Update buttons
            btnUndo.Enabled = True
            btnRedo.Enabled = False
            actionInProgress = False
        End If
    End Sub

    ' Event handler for btnUndo Click event
    Private Sub btnUndo_Click(sender As Object, e As EventArgs) Handles btnUndo.Click
        If undoStack.Count > 0 Then
            ' Save the current state to the redo stack
            Dim currentState As New ActionSnapshot(WallX, WallY,
                                                    wallsHatchStyle, wallsColor1, wallsColor2,
                                                    floorsHatchStyle, floorsColor1, floorsColor2)
            redoStack.Push(currentState)

            ' Restore the previous state from the undo stack
            Dim previousState As ActionSnapshot = undoStack.Pop()
            WallX = CType(previousState.WallX.Clone(), Boolean(,))
            WallY = CType(previousState.WallY.Clone(), Boolean(,))
            wallsHatchStyle = previousState.wallsHatchStyle
            wallsColor1 = previousState.wallsColor1
            wallsColor2 = previousState.wallsColor2
            floorsHatchStyle = previousState.floorsHatchStyle
            floorsColor1 = previousState.floorsColor1
            floorsColor2 = previousState.floorsColor2

            ' Update the lstHatchstyles selection based on the current radio button
            isProgrammaticChange = True
            If rdoWalls.Checked Then
                lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
            ElseIf rdoFloors.Checked Then
                lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
            End If
            isProgrammaticChange = False

            ' Invalidate the grid to redraw
            PicGame.Invalidate()

            ' Enable the Redo button
            btnRedo.Enabled = True

            ' Disable Undo if no more states
            If undoStack.Count = 0 Then
                btnUndo.Enabled = False
            End If
        End If
    End Sub

    ' Event handler for btnRedo Click event
    Private Sub btnRedo_Click(sender As Object, e As EventArgs) Handles btnRedo.Click
        If redoStack.Count > 0 Then
            ' Save the current state to the undo stack
            Dim currentState As New ActionSnapshot(WallX, WallY,
                                                    wallsHatchStyle, wallsColor1, wallsColor2,
                                                    floorsHatchStyle, floorsColor1, floorsColor2)
            undoStack.Push(currentState)

            ' Restore the next state from the redo stack
            Dim nextState As ActionSnapshot = redoStack.Pop()
            WallX = CType(nextState.WallX.Clone(), Boolean(,))
            WallY = CType(nextState.WallY.Clone(), Boolean(,))
            wallsHatchStyle = nextState.wallsHatchStyle
            wallsColor1 = nextState.wallsColor1
            wallsColor2 = nextState.wallsColor2
            floorsHatchStyle = nextState.floorsHatchStyle
            floorsColor1 = nextState.floorsColor1
            floorsColor2 = nextState.floorsColor2

            ' Update the lstHatchstyles selection based on the current radio button
            isProgrammaticChange = True
            If rdoWalls.Checked Then
                lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
            ElseIf rdoFloors.Checked Then
                lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
            End If
            isProgrammaticChange = False

            ' Invalidate the grid to redraw
            PicGame.Invalidate()

            ' Enable the Undo button
            btnUndo.Enabled = True

            ' Disable Redo if no more states
            If redoStack.Count = 0 Then
                btnRedo.Enabled = False
            End If
        End If
    End Sub

    ' ===========================================
    ' End of Undo/Redo Functionality
    ' ===========================================

    ' Event handler for the highlight timer tick
    Private Sub HighlightTimer_Tick(sender As Object, e As EventArgs)
        ' Toggle the highlight color between black and white
        If highlightColor = Color.Black Then
            highlightColor = Color.White
        Else
            highlightColor = Color.Black
        End If
        PicGame.Invalidate()
    End Sub

    ' Event handler for form load
    Private Sub RoomView_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Set up event handlers for PictureBoxes
        For Each ctrl As Control In Me.Controls
            If TypeOf ctrl Is PictureBox AndAlso ctrl.Name.StartsWith("col") Then
                AddHandler ctrl.Click, AddressOf ColorPictureBox_Click
            End If
        Next

        ' Add event handlers for radio buttons
        AddHandler rdoWalls.CheckedChanged, AddressOf RadioButtons_CheckedChanged
        AddHandler rdoFloors.CheckedChanged, AddressOf RadioButtons_CheckedChanged

        ' Populate lstPremades with files from the Premades folder
        PopulatePremadesList()

        ' Disable Undo and Redo buttons at startup
        btnUndo.Enabled = False
        btnRedo.Enabled = False
    End Sub

    ' Event handler for radio buttons CheckedChanged
    Private Sub RadioButtons_CheckedChanged(sender As Object, e As EventArgs)
        If isProgrammaticChange Then
            Return
        End If

        ' Update the lstHatchstyles selection
        isProgrammaticChange = True
        If rdoWalls.Checked Then
            lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
        ElseIf rdoFloors.Checked Then
            lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
        End If
        isProgrammaticChange = False

        ' Redraw the grid
        PicGame.Invalidate()
    End Sub

    ' Method to populate lstPremades
    Private Sub PopulatePremadesList()
        Try
            lstPremades.Items.Clear()
            If Directory.Exists(premadesFolderPath) Then
                Dim files As String() = Directory.GetFiles(premadesFolderPath, "*.premade")
                For Each file As String In files
                    ' Add just the file name without the extension
                    lstPremades.Items.Add(System.IO.Path.GetFileNameWithoutExtension(file))
                Next
            End If
        Catch ex As Exception
            MessageBox.Show("Error loading premades: " & ex.Message)
        End Try
    End Sub

    ' Event handler for lstPremades SelectedIndexChanged
    Private Sub lstPremades_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstPremades.SelectedIndexChanged
        If lstPremades.SelectedItem IsNot Nothing Then
            Dim selectedPremade As String = lstPremades.SelectedItem.ToString()
            LoadPremade(selectedPremade)
        End If
    End Sub

    ' Method to load a premade file (only colors and hatch styles)
    Private Sub LoadPremade(premadeName As String)
        BeginAction()
        Try
            Dim filePath As String = System.IO.Path.Combine(premadesFolderPath, premadeName & ".premade")
            If File.Exists(filePath) Then
                ' Read and parse the premade file
                Using reader As New StreamReader(filePath)
                    Dim line As String
                    While (Not reader.EndOfStream)
                        line = reader.ReadLine()
                        If line.StartsWith("[Walls]", StringComparison.InvariantCultureIgnoreCase) Then
                            ' Read walls settings
                            LoadWallsSettings(reader)
                        ElseIf line.StartsWith("[Floors]", StringComparison.InvariantCultureIgnoreCase) Then
                            ' Read floors settings
                            LoadFloorsSettings(reader)
                        End If
                    End While
                End Using

                ' Update UI elements based on selection
                isProgrammaticChange = True
                If rdoWalls.Checked Then
                    lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
                ElseIf rdoFloors.Checked Then
                    lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
                End If
                isProgrammaticChange = False

                PicGame.Invalidate()
            Else
                MessageBox.Show("Premade file not found: " & filePath)
            End If
        Catch ex As Exception
            MessageBox.Show("Error loading premade: " & ex.Message)
        End Try
        EndAction()
    End Sub

    ' Method to load walls settings from the premade file
    Private Sub LoadWallsSettings(reader As StreamReader)
        Dim line As String
        While (Not reader.EndOfStream)
            line = reader.ReadLine()
            If String.IsNullOrWhiteSpace(line) OrElse line.StartsWith("[") Then
                ' Stop if blank line or next section
                Exit While
            End If
            Dim parts() As String = line.Split(":"c)
            If parts.Length >= 2 Then
                Dim key As String = parts(0).Trim()
                Dim value As String = parts(1).Trim()
                Select Case key
                    Case "HatchStyle"
                        wallsHatchStyle = CType([Enum].Parse(GetType(HatchStyle), value), HatchStyle)
                    Case "Color1"
                        wallsColor1 = ParseColor(value)
                    Case "Color2"
                        wallsColor2 = ParseColor(value)
                End Select
            End If
        End While
    End Sub

    ' Method to load floors settings from the premade file
    Private Sub LoadFloorsSettings(reader As StreamReader)
        Dim line As String
        While (Not reader.EndOfStream)
            line = reader.ReadLine()
            If String.IsNullOrWhiteSpace(line) OrElse line.StartsWith("[") Then
                ' Stop if blank line or next section
                Exit While
            End If
            Dim parts() As String = line.Split(":"c)
            If parts.Length >= 2 Then
                Dim key As String = parts(0).Trim()
                Dim value As String = parts(1).Trim()
                Select Case key
                    Case "HatchStyle"
                        floorsHatchStyle = CType([Enum].Parse(GetType(HatchStyle), value), HatchStyle)
                    Case "Color1"
                        floorsColor1 = ParseColor(value)
                    Case "Color2"
                        floorsColor2 = ParseColor(value)
                End Select
            End If
        End While
    End Sub

    ' Function to parse a color from a string
    Private Function ParseColor(value As String) As Color
        ' Value expected in the format "A,R,G,B" or "R,G,B"
        Dim components() As String = value.Split(","c)
        If components.Length = 4 Then
            ' ARGB
            Dim a As Byte = Byte.Parse(components(0))
            Dim r As Byte = Byte.Parse(components(1))
            Dim g As Byte = Byte.Parse(components(2))
            Dim b As Byte = Byte.Parse(components(3))
            Return Color.FromArgb(a, r, g, b)
        ElseIf components.Length = 3 Then
            ' RGB
            Dim r As Byte = Byte.Parse(components(0))
            Dim g As Byte = Byte.Parse(components(1))
            Dim b As Byte = Byte.Parse(components(2))
            Return Color.FromArgb(r, g, b)
        Else
            Throw New FormatException("Invalid color format: " & value)
        End If
    End Function
    ' Event handler for the Save Premade button click
    Private Sub btnSavePremade_Click(sender As Object, e As EventArgs) Handles btnSavePremade.Click
        Using saveFileDialog As New SaveFileDialog()
            saveFileDialog.InitialDirectory = premadesFolderPath
            saveFileDialog.Filter = "Premade Files (*.premade)|*.premade|All Files (*.*)|*.*"
            If saveFileDialog.ShowDialog() = DialogResult.OK Then
                SavePremadeToFile(saveFileDialog.FileName)
                ' Refresh the premades list
                PopulatePremadesList()
            End If
        End Using
    End Sub

    ' Method to save the premade settings to a file (only colors and hatch styles)
    Private Sub SavePremadeToFile(filename As String)
        Try
            ' Ensure the directory exists
            Dim directoryPath As String = System.IO.Path.GetDirectoryName(filename)
            If Not Directory.Exists(directoryPath) Then
                Directory.CreateDirectory(directoryPath)
            End If

            Using writer As New StreamWriter(filename)
                ' Save Walls settings
                writer.WriteLine("[Walls]")
                writer.WriteLine("HatchStyle: " & wallsHatchStyle.ToString())
                writer.WriteLine("Color1: " & ColorToString(wallsColor1))
                writer.WriteLine("Color2: " & ColorToString(wallsColor2))
                writer.WriteLine()

                ' Save Floors settings
                writer.WriteLine("[Floors]")
                writer.WriteLine("HatchStyle: " & floorsHatchStyle.ToString())
                writer.WriteLine("Color1: " & ColorToString(floorsColor1))
                writer.WriteLine("Color2: " & ColorToString(floorsColor2))
            End Using

            MessageBox.Show("Premade saved successfully!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information)
        Catch ex As Exception
            MessageBox.Show("Error saving premade: " & ex.Message)
        End Try
    End Sub

    ' Function to convert a Color to a string
    Private Function ColorToString(color As Color) As String
        ' Return in the format "A,R,G,B"
        Return String.Format("{0},{1},{2},{3}", color.A, color.R, color.G, color.B)
    End Function

    ' Event handler for btnRandom Click event
    Private Sub btnRandom_Click(sender As Object, e As EventArgs) Handles btnRandom.Click
        BeginAction()

        Dim random As New Random()

        ' Get array of possible HatchStyles
        Dim hatchStyles As Array = [Enum].GetValues(GetType(HatchStyle))

        ' Randomly select walls hatch style
        wallsHatchStyle = CType(hatchStyles.GetValue(random.Next(hatchStyles.Length)), HatchStyle)
        ' Randomly select walls colors
        wallsColor1 = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256))
        wallsColor2 = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256))

        ' Randomly select floors hatch style
        floorsHatchStyle = CType(hatchStyles.GetValue(random.Next(hatchStyles.Length)), HatchStyle)
        ' Randomly select floors colors
        floorsColor1 = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256))
        floorsColor2 = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256))

        ' Update the lstHatchstyles selection
        isProgrammaticChange = True
        If rdoWalls.Checked Then
            lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
        ElseIf rdoFloors.Checked Then
            lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
        End If
        isProgrammaticChange = False

        ' Redraw the grid
        PicGame.Invalidate()

        EndAction()
    End Sub

    ' Event handler for lstHatchstyles selection change
    Private Sub lstHatchstyles_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstHatchstyles.SelectedIndexChanged
        If isProgrammaticChange Then
            Return
        End If

        BeginAction()

        Dim selectedHatchName As String = lstHatchstyles.SelectedItem.ToString()
        Dim selectedHatchStyle As HatchStyle = CType([Enum].Parse(GetType(HatchStyle), selectedHatchName), HatchStyle)

        If rdoWalls.Checked Then
            wallsHatchStyle = selectedHatchStyle
        ElseIf rdoFloors.Checked Then
            floorsHatchStyle = selectedHatchStyle
        End If

        PicGame.Invalidate()

        EndAction()
    End Sub

    ' Event handler for PictureBox clicks
    Private Sub ColorPictureBox_Click(sender As Object, e As EventArgs)
        BeginAction()

        Dim pb As PictureBox = CType(sender, PictureBox)
        Dim pbName As String = pb.Name
        If pbName.StartsWith("col") Then
            Dim colNumStr As String = pbName.Substring(3)
            Dim colNum As Integer
            If Integer.TryParse(colNumStr, colNum) Then
                Dim pbColor As Color = pb.BackColor
                If colNum >= 1 AndAlso colNum <= 48 Then
                    ' Set Color1
                    If rdoWalls.Checked Then
                        wallsColor1 = pbColor
                    ElseIf rdoFloors.Checked Then
                        floorsColor1 = pbColor
                    End If
                ElseIf colNum >= 51 AndAlso colNum <= 98 Then
                    ' Set Color2
                    If rdoWalls.Checked Then
                        wallsColor2 = pbColor
                    ElseIf rdoFloors.Checked Then
                        floorsColor2 = pbColor
                    End If
                End If
                PicGame.Invalidate()
            End If
        End If

        EndAction()
    End Sub

    ' Event handler for PicGame's Paint event
    Private Sub PicGame_Paint(sender As Object, e As PaintEventArgs) Handles PicGame.Paint
        ' Calculate the square size based on the size of the PictureBox
        CalculateSquareSizes()

        ' Set SmoothingMode to None to prevent gaps due to anti-aliasing
        e.Graphics.SmoothingMode = SmoothingMode.None

        ' Draw the floors
        DrawFloors(e.Graphics)

        ' Draw the walls
        DrawWalls(e.Graphics)

        ' Draw intersections
        DrawIntersections(e.Graphics)

        ' Draw highlights
        DrawHighlight(e.Graphics)
    End Sub

    ' Event handler for resizing the form
    Private Sub RoomView_Resize(sender As Object, e As EventArgs) Handles Me.Resize
        If PicGame.ClientSize.Width > 0 AndAlso PicGame.ClientSize.Height > 0 Then
            CalculateSquareSizes()
            PicGame.Invalidate() ' Redraw the PictureBox
        End If
    End Sub

    ' Method to calculate square sizes
    Private Sub CalculateSquareSizes()
        If PicGame.ClientSize.Width > 0 AndAlso PicGame.ClientSize.Height > 0 Then
            SQSizeX = (PicGame.ClientSize.Width - 2 * INDENT - WALL_THICKNESS * (SQCOUNTX + 1)) \ SQCOUNTX
            SQSizeY = (PicGame.ClientSize.Height - 2 * INDENT - WALL_THICKNESS * (SQCOUNTY + 1)) \ SQCOUNTY
        End If
    End Sub

    ' Method to draw the walls
    Private Sub DrawWalls(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        ' Use stored HatchBrush for walls
        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
            ' Use stored HatchBrush for floors
            Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                ' Draw vertical walls (WallX)
                For i As Integer = 0 To SQCOUNTX
                    For j As Integer = 0 To SQCOUNTY - 1
                        Dim x As Integer = xOrigin + i * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2
                        Dim y As Integer = yOrigin + j * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2

                        If WallX(i, j) Then
                            ' Vertical wall exists, draw it
                            g.FillRectangle(wallBrush, x, y, WALL_THICKNESS, SQSizeY)
                        Else
                            ' Wall does not exist, fill with floor pattern
                            g.FillRectangle(floorBrush, x, y, WALL_THICKNESS, SQSizeY)
                        End If
                    Next
                Next

                ' Draw horizontal walls (WallY)
                For i As Integer = 0 To SQCOUNTX - 1
                    For j As Integer = 0 To SQCOUNTY
                        Dim x As Integer = xOrigin + i * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                        Dim y As Integer = yOrigin + j * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2

                        If WallY(i, j) Then
                            ' Horizontal wall exists, draw it
                            g.FillRectangle(wallBrush, x, y, SQSizeX, WALL_THICKNESS)
                        Else
                            ' Wall does not exist, fill with floor pattern
                            g.FillRectangle(floorBrush, x, y, SQSizeX, WALL_THICKNESS)
                        End If
                    Next
                Next
            End Using
        End Using
    End Sub

    ' Method to draw the floors
    Private Sub DrawFloors(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        ' Use stored HatchBrush for floors
        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
            For i As Integer = 0 To SQCOUNTX - 1
                For j As Integer = 0 To SQCOUNTY - 1
                    Dim x As Integer = xOrigin + i * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                    Dim y As Integer = yOrigin + j * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2

                    ' Draw the floor cell
                    g.FillRectangle(floorBrush, x, y, SQSizeX, SQSizeY)
                Next
            Next
        End Using
    End Sub

    ' Method to draw intersection squares
    Private Sub DrawIntersections(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        ' Use stored HatchBrush for walls
        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
            For i As Integer = 0 To SQCOUNTX
                For j As Integer = 0 To SQCOUNTY
                    Dim x As Integer = xOrigin + i * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2
                    Dim y As Integer = yOrigin + j * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2

                    ' Draw intersection square with wall pattern
                    g.FillRectangle(wallBrush, x, y, WALL_THICKNESS, WALL_THICKNESS)
                Next
            Next
        End Using
    End Sub

    ' Method to draw the highlight
    Private Sub DrawHighlight(g As Graphics)
        If Not isMouseInsideGrid Then
            Return
        End If

        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        Using highlightPen As New Pen(highlightColor, 2)
            highlightPen.DashStyle = DashStyle.Dash

            If highlightedWallX Then
                ' Vertical wall
                Dim x As Integer = xOrigin + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2
                Dim y As Integer = yOrigin + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim highlightRect As New Rectangle(x, y, WALL_THICKNESS, SQSizeY)
                g.DrawRectangle(highlightPen, highlightRect)
            ElseIf highlightedWallY Then
                ' Horizontal wall
                Dim x As Integer = xOrigin + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim y As Integer = yOrigin + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2
                Dim highlightRect As New Rectangle(x, y, SQSizeX, WALL_THICKNESS)
                g.DrawRectangle(highlightPen, highlightRect)
            Else
                ' Square
                Dim x As Integer = xOrigin + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim y As Integer = yOrigin + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim highlightRect As New Rectangle(x, y, SQSizeX, SQSizeY)
                g.DrawRectangle(highlightPen, highlightRect)
            End If
        End Using
    End Sub

    ' Event handler for PicGame's MouseMove event
    Private Sub PicGame_MouseMove(sender As Object, e As MouseEventArgs) Handles PicGame.MouseMove
        ' Initialize as False at the start of the method
        isMouseInsideGrid = False

        ' Adjust for indentation
        Dim mouseX As Integer = e.X - INDENT
        Dim mouseY As Integer = e.Y - INDENT

        ' Early exit if the mouse is outside the grid area
        If mouseX < 0 OrElse mouseY < 0 Then
            STB.Text = "Outside Grid"
            Exit Sub
        End If

        Dim cellSizeX As Integer = SQSizeX + WALL_THICKNESS
        Dim cellSizeY As Integer = SQSizeY + WALL_THICKNESS

        Dim cellX As Integer = mouseX \ cellSizeX
        Dim cellY As Integer = mouseY \ cellSizeY

        If cellX >= SQCOUNTX OrElse cellY >= SQCOUNTY Then
            STB.Text = "Outside Grid"
            Exit Sub
        End If

        Dim offsetX As Integer = mouseX Mod cellSizeX
        Dim offsetY As Integer = mouseY Mod cellSizeY

        highlightedWallX = False
        highlightedWallY = False

        If offsetX < WALL_THICKNESS Then
            ' Vertical wall (WallX)
            If cellX = 0 OrElse cellX = SQCOUNTX Then
                ' Border wall, do not highlight
                STB.Text = "Outside Grid"
                Exit Sub
            End If
            highlightedWallX = True
            highlightedX = cellX
            highlightedY = cellY
            STB.Text = $"WallX({cellX},{cellY})"
            isMouseInsideGrid = True

            ' Wall modification code
            If isRightMouseDown OrElse isLeftMouseDown Then
                If isRightMouseDown Then
                    If WallX(cellX, cellY) Then
                        WallX(cellX, cellY) = False
                        PicGame.Invalidate()
                    End If
                ElseIf isLeftMouseDown Then
                    If Not WallX(cellX, cellY) Then
                        WallX(cellX, cellY) = True
                        PicGame.Invalidate()
                    End If
                End If
            End If

        ElseIf offsetY < WALL_THICKNESS Then
            ' Horizontal wall (WallY)
            If cellY = 0 OrElse cellY = SQCOUNTY Then
                ' Border wall, do not highlight
                STB.Text = "Outside Grid"
                Exit Sub
            End If
            highlightedWallY = True
            highlightedX = cellX
            highlightedY = cellY
            STB.Text = $"WallY({cellX},{cellY})"
            isMouseInsideGrid = True

            ' Wall modification code
            If isRightMouseDown OrElse isLeftMouseDown Then
                If isRightMouseDown Then
                    If WallY(cellX, cellY) Then
                        WallY(cellX, cellY) = False
                        PicGame.Invalidate()
                    End If
                ElseIf isLeftMouseDown Then
                    If Not WallY(cellX, cellY) Then
                        WallY(cellX, cellY) = True
                        PicGame.Invalidate()
                    End If
                End If
            End If

        ElseIf offsetX >= WALL_THICKNESS AndAlso offsetX < cellSizeX AndAlso offsetY >= WALL_THICKNESS AndAlso offsetY < cellSizeY Then
            ' Inside the square
            highlightedX = cellX
            highlightedY = cellY
            STB.Text = $"Square({cellX},{cellY})"
            isMouseInsideGrid = True
        Else
            STB.Text = "Outside Grid"
            Exit Sub
        End If

        ' Redraw to update the highlight
        PicGame.Invalidate()
    End Sub

    ' Event handler for MouseDown on PicGame
    Private Sub PicGame_MouseDown(sender As Object, e As MouseEventArgs) Handles PicGame.MouseDown
        If e.Button = MouseButtons.Right OrElse e.Button = MouseButtons.Left Then
            If Not actionInProgress Then
                BeginAction()
            End If
            isRightMouseDown = (e.Button = MouseButtons.Right)
            isLeftMouseDown = (e.Button = MouseButtons.Left)
        End If
    End Sub

    ' Event handler for MouseUp on PicGame
    Private Sub PicGame_MouseUp(sender As Object, e As MouseEventArgs) Handles PicGame.MouseUp
        If e.Button = MouseButtons.Right OrElse e.Button = MouseButtons.Left Then
            isRightMouseDown = False
            isLeftMouseDown = False

            ' End the action when mouse button is released
            EndAction()
        End If
    End Sub

    Private Sub PicGame_Click(sender As Object, e As EventArgs) Handles PicGame.Click
        ' Handle click events if needed
    End Sub

    ' Event handler for the "New" menu item
    Private Sub mnuNew_Click(sender As Object, e As EventArgs) Handles mnuNew.Click
        BeginAction()

        ' Reset the walls to the initial state
        For i As Integer = 0 To SQCOUNTX
            For j As Integer = 0 To SQCOUNTY
                WallX(i, j) = True
                WallY(i, j) = True
            Next
        Next

        ' Clear the current file name
        currentFilename = ""

        ' Redraw the grid
        PicGame.Invalidate()

        EndAction()
    End Sub

    ' Event handler for the "Save" menu item
    Private Sub mnuSave_Click(sender As Object, e As EventArgs) Handles mnuSave.Click
        If String.IsNullOrEmpty(currentFilename) Then
            ' If no filename, prompt the user to "Save As"
            mnuSaveAs_Click(sender, e)
        Else
            ' Save to the existing file
            SaveGridToFile(currentFilename)
        End If
    End Sub

    ' Event handler for the "Save As" menu item
    Private Sub mnuSaveAs_Click(sender As Object, e As EventArgs) Handles mnuSaveAs.Click
        Using saveFileDialog As New SaveFileDialog()
            saveFileDialog.Filter = "Room Files (*.room)|*.room|All Files (*.*)|*.*"
            If saveFileDialog.ShowDialog() = DialogResult.OK Then
                currentFilename = saveFileDialog.FileName
                SaveGridToFile(currentFilename)
            End If
        End Using
    End Sub

    ' Event handler for the "Load" menu item
    Private Sub mnuLoad_Click(sender As Object, e As EventArgs) Handles mnuLoad.Click
        BeginAction()
        Using openFileDialog As New OpenFileDialog()
            openFileDialog.Filter = "Room Files (*.room)|*.room|All Files (*.*)|*.*"
            If openFileDialog.ShowDialog() = DialogResult.OK Then
                currentFilename = openFileDialog.FileName
                LoadGridFromFile(currentFilename)
                PicGame.Invalidate() ' Redraw the grid
            End If
        End Using
        EndAction()
    End Sub

    ' Method to save the grid state to a file
    Private Sub SaveGridToFile(filename As String)
        Try
            Using writer As New StreamWriter(filename)
                ' Save WallX
                writer.WriteLine("WallX")
                For j As Integer = 0 To SQCOUNTY - 1
                    Dim line As String = ""
                    For i As Integer = 0 To SQCOUNTX
                        line &= If(WallX(i, j), "1", "0")
                    Next
                    writer.WriteLine(line)
                Next

                ' Save WallY
                writer.WriteLine("WallY")
                For j As Integer = 0 To SQCOUNTY
                    Dim line As String = ""
                    For i As Integer = 0 To SQCOUNTX - 1
                        line &= If(WallY(i, j), "1", "0")
                    Next
                    writer.WriteLine(line)
                Next
            End Using
        Catch ex As Exception
            MessageBox.Show("Error saving file: " & ex.Message)
        End Try
    End Sub

    ' Method to load the grid state from a file
    Private Sub LoadGridFromFile(filename As String)
        Try
            Using reader As New StreamReader(filename)
                ' Read "WallX"
                Dim line As String = reader.ReadLine()
                If line <> "WallX" Then
                    Throw New Exception("Invalid file format: expected 'WallX'")
                End If

                ' Read WallX data
                For j As Integer = 0 To SQCOUNTY - 1
                    line = reader.ReadLine()
                    If line Is Nothing OrElse line.Length < SQCOUNTX + 1 Then
                        Throw New Exception("Invalid WallX data at row " & j)
                    End If
                    For i As Integer = 0 To SQCOUNTX
                        Dim ch As Char = line(i)
                        WallX(i, j) = (ch = "1"c)
                    Next
                Next

                ' Read "WallY"
                line = reader.ReadLine()
                If line <> "WallY" Then
                    Throw New Exception("Invalid file format: expected 'WallY'")
                End If

                ' Read WallY data
                For j As Integer = 0 To SQCOUNTY
                    line = reader.ReadLine()
                    If line Is Nothing OrElse line.Length < SQCOUNTX Then
                        Throw New Exception("Invalid WallY data at row " & j)
                    End If
                    For i As Integer = 0 To SQCOUNTX - 1
                        Dim ch As Char = line(i)
                        WallY(i, j) = (ch = "1"c)
                    Next
                Next
            End Using
        Catch ex As Exception
            MessageBox.Show("Error loading file: " & ex.Message)
        End Try
    End Sub

    ' Optional: Handle form closing to dispose of the timer
    Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
        MyBase.OnFormClosing(e)
        If highlightTimer IsNot Nothing Then
            highlightTimer.Stop()
            highlightTimer.Dispose()
        End If
    End Sub

End Class

Couple of questions about the above.

I've added a Button called with text Test (btnTest)

When pressed it will open a new form frmGame. It's a blank form. No border, no nothing. It will start Maximised. 

What I want, is the above code. To run in the test form. Obviously 90% of the code is not needed as it's a form to test the game. But. the game daata must fill the screen. Can you do this? Also.. I'm wondering if I should create some mudules for shared code, rather than duplicating.