Option Explicit On
Option Strict On

Imports System.IO
Imports NAudio.Wave
Imports NAudio.Wave.SampleProviders

Public Class frmMusic

    ' Define fields for audio processing
    Private ReadOnly projectDir As String = GetProjectDirectory()

    Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Sound/Music")
    Private ReadOnly IconsFolder As String = Path.Combine(projectDir, "Data", "Icons")

    Private waveOut As WaveOutEvent
    Private audioFileReader As AudioFileReader
    Private sampleChannel As SampleChannel

    ' Properties to return volume and gain levels
    Public Property AudioFile As String

    Public Property VolumeLevel As Integer
    Public Property GainLevel As Integer

    ' Fields for the moving line
    Private playbackTimer As New Timer()

    Private currentPosition As Double = 0
    Private totalDuration As Double = 0
    Private ReadOnly darkGreenPen As New Pen(Color.DarkGreen, 2)

    Private isPlaying As Boolean = False
    Private isRepeating As Boolean = False

    Private Function GetProjectDirectory() As String
        Dim dir As New DirectoryInfo(Application.StartupPath)
        For i As Integer = 1 To 4
            If dir.Parent IsNot Nothing Then
                dir = dir.Parent
            End If
        Next
        Return dir.FullName
    End Function

    Public Property InitialVolume As Integer
    Public Property InitialGain As Integer
    Public Property InitialSelectedTrack As String

    Private Sub frmMusic_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))

        ' Disable the "new row" at the bottom and allow a vertical scrollbar
        dgSound.AllowUserToAddRows = False
        dgSound.ScrollBars = ScrollBars.Vertical

        ' Initialize DataGridView columns
        dgSound.Columns.Clear()

        ' Create the columns
        Dim colFilename As New DataGridViewTextBoxColumn()
        colFilename.Name = "Filename"
        colFilename.HeaderText = "Filename"

        Dim colLength As New DataGridViewTextBoxColumn()
        colLength.Name = "Length"
        colLength.HeaderText = "Length"

        Dim colType As New DataGridViewTextBoxColumn()
        colType.Name = "Type"
        colType.HeaderText = "Type"

        ' Add the columns to the DataGridView
        dgSound.Columns.Add(colFilename)
        dgSound.Columns.Add(colLength)
        dgSound.Columns.Add(colType)

        ' Load music files into DataGridView
        LoadMusicFiles()

        ' Set up other controls
        TBVolume.Minimum = 0
        TBVolume.Maximum = 100
        TBVolume.Value = 100

        TBGain.Minimum = 0
        TBGain.Maximum = 30
        TBGain.Value = 5

        ' Initialize controls with the initial settings
        If Not String.IsNullOrEmpty(InitialSelectedTrack) Then
            Dim fileNameWithoutExtension = Path.GetFileNameWithoutExtension(InitialSelectedTrack)
            Dim fileType = Path.GetExtension(InitialSelectedTrack).Replace(".", "").ToLower()

            ' Find the row in the DataGridView that matches the initial selected track
            For Each row As DataGridViewRow In dgSound.Rows
                If row.Cells("Filename").Value.ToString() = fileNameWithoutExtension AndAlso row.Cells("Type").Value.ToString().ToLower() = fileType Then
                    dgSound.CurrentCell = row.Cells("Filename")
                    dgSound.Rows(row.Index).Selected = True
                    LoadAndPlayAudio(fileNameWithoutExtension, fileType)
                    Exit For
                End If
            Next
        End If

        TBVolume.Value = InitialVolume
        TBGain.Value = InitialGain
        UpdateVolumeLabel()
        UpdateGainLabel()
        UpdateVolumeLabel()
        UpdateGainLabel()

        DrawCenterLine() ' Initialize with a center line

        playbackTimer.Interval = 500 ' Update every 500ms
        AddHandler playbackTimer.Tick, AddressOf PlaybackTimer_Tick

        AdjustColumnWidths() ' Call AdjustColumnWidths after columns are created and data is loaded
    End Sub
    Private Sub frmMusic_Resize(sender As Object, e As EventArgs) Handles MyBase.Resize
        AdjustColumnWidths()
    End Sub

    Private Sub AdjustColumnWidths()
        If dgSound.Columns.Count = 0 Then Exit Sub

        Dim scrollbarWidth As Integer = SystemInformation.VerticalScrollBarWidth
        Dim availableWidth As Integer = dgSound.ClientSize.Width - scrollbarWidth

        ' Set Filename column width to 60%
        dgSound.Columns("Filename").Width = CInt(availableWidth * 0.6)

        ' Calculate remaining width for other columns
        Dim remainingWidth As Integer = availableWidth - dgSound.Columns("Filename").Width

        ' Divide the remaining width equally between Length and Type
        dgSound.Columns("Length").Width = CInt(remainingWidth / 2)
        dgSound.Columns("Type").Width = remainingWidth - dgSound.Columns("Length").Width
    End Sub
    Private Sub LoadMusicFiles()
        Dim allowedExtensions As String() = {".mp3", ".wav"}
        dgSound.Rows.Clear()

        If Directory.Exists(musicFolder) Then
            Dim musicFiles = Directory.GetFiles(musicFolder)
            For Each file In musicFiles
                If allowedExtensions.Contains(Path.GetExtension(file).ToLower()) Then
                    Dim fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file)
                    Dim fileType = Path.GetExtension(file).Replace(".", "").ToUpper()

                    Using reader As New AudioFileReader(file)
                        Dim duration As String = $"{reader.TotalTime.Minutes:D2}:{reader.TotalTime.Seconds:D2}"
                        Dim unused1 = dgSound.Rows.Add(fileNameWithoutExtension, duration, fileType)
                    End Using
                End If
            Next
        Else
            Dim unused = MessageBox.Show("Music folder doesn't exist.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub

    Private Sub dgSound_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgSound.CellClick
        If e.RowIndex >= 0 AndAlso e.RowIndex < dgSound.Rows.Count Then
            Dim selectedRow = dgSound.Rows(e.RowIndex)
            Dim fileNameWithoutExtension = selectedRow.Cells("Filename").Value.ToString()
            Dim fileType = selectedRow.Cells("Type").Value.ToString().ToLower()

            ' Call the method with the correct parameters
            LoadAndPlayAudio(fileNameWithoutExtension, fileType)

            ' Update buttons
            btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Pause.png"))
            isPlaying = True
            btnPlayPause.BorderStyle = BorderStyle.Fixed3D
            btnStop.BackColor = DefaultBackColor
            btnStop.BorderStyle = BorderStyle.None
            If isRepeating Then
                btnRepeat.BackColor = Color.LightGreen
                btnRepeat.BorderStyle = BorderStyle.Fixed3D
            Else
                btnRepeat.BackColor = DefaultBackColor
                btnRepeat.BorderStyle = BorderStyle.None
            End If
        End If
    End Sub

    Private Sub LoadAndPlayAudio(selectedFileName As String, fileType As String)
        playbackTimer.Stop()

        Try
            ' Ensure previous audio resources are disposed of safely
            If waveOut IsNot Nothing Then
                RemoveHandler waveOut.PlaybackStopped, AddressOf WaveOut_PlaybackStopped
                waveOut.Stop()
                waveOut.Dispose()
                waveOut = Nothing
            End If

            If audioFileReader IsNot Nothing Then
                audioFileReader.Dispose()
                audioFileReader = Nothing
            End If

            ' Construct the complete file path
            Dim completeFilePath As String = Path.Combine(musicFolder, selectedFileName & "." & fileType)

            If File.Exists(completeFilePath) Then
                audioFileReader = New AudioFileReader(completeFilePath)
                sampleChannel = New SampleChannel(audioFileReader, True)

                ' Apply gain
                Dim gainMultiplier As Single = TBGain.Value / 10.0F
                sampleChannel.Volume = gainMultiplier

                ' Initialize and start playback
                waveOut = New WaveOutEvent()
                waveOut.Init(New SampleToWaveProvider(sampleChannel))
                waveOut.Volume = TBVolume.Value / 100.0F

                totalDuration = audioFileReader.TotalTime.TotalSeconds
                currentPosition = 0 ' Reset

                ' Draw initial waveform
                DrawWaveform(completeFilePath, gainMultiplier)
                AudioFile = selectedFileName & "." & fileType

                AddHandler waveOut.PlaybackStopped, AddressOf WaveOut_PlaybackStopped

                waveOut.Play()
                playbackTimer.Start()
            Else
                Dim unused1 = MessageBox.Show("Selected audio file not found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                DrawCenterLine()
            End If
        Catch ex As Exception
            Dim unused = MessageBox.Show($"Error loading audio: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

    ' Ensure only one version of this method exists
    Private Sub WaveOut_PlaybackStopped(sender As Object, e As StoppedEventArgs)
        If IsHandleCreated AndAlso Not IsDisposed AndAlso audioFileReader IsNot Nothing Then
            ' Determine if track finished playing normally
            If Math.Abs(audioFileReader.CurrentTime.TotalSeconds - audioFileReader.TotalTime.TotalSeconds) < 0.1 Then
                currentPosition = totalDuration
                playbackTimer.Stop()
                UpdateTimerLabel()

                If isRepeating Then
                    Try
                        ' Reset and play again if repeating
                        audioFileReader.CurrentTime = TimeSpan.Zero
                        currentPosition = 0
                        waveOut.Play()
                        playbackTimer.Start()
                        btnPlayPause.BorderStyle = BorderStyle.Fixed3D
                    Catch ex As Exception
                        Dim unused = MessageBox.Show($"Error repeating audio: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    End Try
                Else
                    DrawCenterLine()
                    btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))
                    isPlaying = False
                    btnPlayPause.BorderStyle = BorderStyle.FixedSingle
                End If
            End If
        End If
    End Sub

    Private Sub TBVolume_Scroll(sender As Object, e As EventArgs) Handles TBVolume.Scroll
        If waveOut IsNot Nothing Then
            waveOut.Volume = TBVolume.Value / 100.0F
        End If
        UpdateVolumeLabel()
    End Sub

    Private Sub TBGain_Scroll(sender As Object, e As EventArgs) Handles TBGain.Scroll
        If sampleChannel IsNot Nothing Then
            Dim gainMultiplier As Single = TBGain.Value / 10.0F
            sampleChannel.Volume = gainMultiplier
            DrawWaveform(Path.Combine(musicFolder, AudioFile), gainMultiplier)
        End If
        UpdateGainLabel()
    End Sub

    Private Sub UpdateVolumeLabel()
        lblVolume.Text = "Volume: " & TBVolume.Value
    End Sub

    Private Sub UpdateGainLabel()
        lblGain.Text = "Gain: " & TBGain.Value
    End Sub

    Private Async Sub DrawWaveform(filePath As String, gainMultiplier As Single)
        If Not IsHandleCreated OrElse picWav.IsDisposed OrElse picWav.Width = 0 OrElse picWav.Height = 0 Then Exit Sub

        Await Task.Run(Sub()
                           Try
                               Using reader As New AudioFileReader(filePath)
                                   Dim buffer As Single() = New Single(16383) {}
                                   Dim allSamples As New List(Of Single)()
                                   Dim samplesRead As Integer

                                   Do
                                       samplesRead = reader.Read(buffer, 0, buffer.Length)
                                       allSamples.AddRange(buffer.Take(samplesRead))
                                   Loop While samplesRead > 0

                                   Dim bmp As New Bitmap(picWav.Width, picWav.Height)
                                   Using g As Graphics = Graphics.FromImage(bmp)
                                       g.Clear(Color.Black)
                                       Dim midY As Integer = picWav.Height \ 2
                                       Dim samplesPerPixel As Integer = Math.Max(1, allSamples.Count \ (picWav.Width * reader.WaveFormat.Channels))

                                       For x As Integer = 0 To picWav.Width - 1
                                           Dim startIndex As Integer = x * samplesPerPixel * reader.WaveFormat.Channels
                                           Dim maxAmplitude As Single = allSamples.Skip(startIndex).Take(samplesPerPixel).Max(Function(s) Math.Abs(s))
                                           maxAmplitude *= gainMultiplier
                                           maxAmplitude = Math.Min(maxAmplitude, 1.0F)

                                           Dim amplitudeHeight As Integer = CInt(maxAmplitude * picWav.Height / 2)
                                           g.DrawLine(Pens.White, x, midY - amplitudeHeight, x, midY + amplitudeHeight)
                                       Next

                                       ' Draw the green line
                                       Dim greenLineX As Integer = CInt(currentPosition / totalDuration * picWav.Width)
                                       g.DrawLine(darkGreenPen, greenLineX, 0, greenLineX, picWav.Height)
                                   End Using

                                   If IsHandleCreated Then
                                       Invoke(Sub()
                                                  If Not picWav.IsDisposed Then
                                                      picWav.Image?.Dispose()
                                                      picWav.Image = bmp
                                                  End If
                                              End Sub)
                                   End If
                               End Using
                           Catch ex As Exception
                               Console.WriteLine($"Drawing waveform error: {ex.Message}")
                           End Try
                       End Sub)
    End Sub

    Private Sub DrawCenterLine()
        If picWav.Width = 0 OrElse picWav.Height = 0 Then Exit Sub

        Dim bmp As New Bitmap(picWav.Width, picWav.Height)
        Using g As Graphics = Graphics.FromImage(bmp)
            g.Clear(Color.Black)
            Dim midY As Integer = CInt(picWav.Height / 2)
            g.DrawLine(Pens.White, 0, midY, picWav.Width, midY)
        End Using
        picWav.Image = bmp
    End Sub

    Private Sub frmMusic_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        ' Stop timer and clean up resources
        playbackTimer.Stop()
        RemoveHandler playbackTimer.Tick, AddressOf PlaybackTimer_Tick

        Try
            If waveOut IsNot Nothing Then
                RemoveHandler waveOut.PlaybackStopped, AddressOf WaveOut_PlaybackStopped
                waveOut.Stop()
                waveOut.Dispose()
                waveOut = Nothing
            End If

            If audioFileReader IsNot Nothing Then
                audioFileReader.Dispose()
                audioFileReader = Nothing
            End If
        Catch ex As Exception
            Console.WriteLine($"Error during form closing: {ex.Message}")
        End Try

        VolumeLevel = TBVolume.Value
        GainLevel = TBGain.Value
        DialogResult = DialogResult.OK
    End Sub

    Private Sub UpdateTimerLabel()
        If audioFileReader IsNot Nothing Then
            Dim elapsed As TimeSpan = audioFileReader.CurrentTime
            Dim total As TimeSpan = audioFileReader.TotalTime
            lblTimer.Text = String.Format("{0:D2}:{1:D2} / {2:D2}:{3:D2}", elapsed.Minutes, elapsed.Seconds, total.Minutes, total.Seconds)
        Else
            lblTimer.Text = "00:00 / 00:00"
        End If
    End Sub

    Private Sub PlaybackTimer_Tick(sender As Object, e As EventArgs)
        If audioFileReader IsNot Nothing AndAlso waveOut IsNot Nothing AndAlso waveOut.PlaybackState = PlaybackState.Playing Then
            currentPosition = audioFileReader.CurrentTime.TotalSeconds
            UpdateTimerLabel()

            ' Ensure the form and controls are still valid
            If IsHandleCreated Then
                Invoke(Sub() DrawWaveform(Path.Combine(musicFolder, AudioFile), TBGain.Value / 10.0F))
            End If
        End If
    End Sub

    Private Sub picWav_MouseDown(sender As Object, e As MouseEventArgs) Handles picWav.MouseDown
        ' Ensure audio is loaded and playing
        If audioFileReader Is Nothing OrElse waveOut Is Nothing Then Exit Sub

        ' Calculate the new position based on the click
        Dim newPositionRatio As Double = e.X / picWav.Width
        newPositionRatio = Math.Max(0, Math.Min(newPositionRatio, 1)) ' Clamp between 0 and 1
        currentPosition = totalDuration * newPositionRatio

        ' Set the audio playback position
        audioFileReader.CurrentTime = TimeSpan.FromSeconds(currentPosition)

        ' Redraw the waveform with the new position
        If Not String.IsNullOrEmpty(AudioFile) Then
            DrawWaveform(Path.Combine(musicFolder, AudioFile), TBGain.Value / 10.0F)
        End If

        ' Ensure the playback timer continues
        playbackTimer.Start()

        ' Restart playback from the new position
        If waveOut.PlaybackState <> PlaybackState.Playing Then
            waveOut.Play()
        End If
    End Sub

    Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
        If waveOut IsNot Nothing Then
            waveOut.Stop()
            audioFileReader.CurrentTime = TimeSpan.Zero
            currentPosition = 0
            playbackTimer.Stop()
            UpdateTimerLabel()
            DrawWaveform(Path.Combine(musicFolder, AudioFile), TBGain.Value / 10.0F)
            btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))
            isPlaying = False
            btnPlayPause.BorderStyle = BorderStyle.FixedSingle

            ' Dispose and reinitialize waveOut and audioFileReader
            waveOut.Dispose()
            waveOut = Nothing
            audioFileReader.Dispose()
            audioFileReader = Nothing
        End If

        ' Reset back colors
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.Fixed3D
        If isRepeating Then
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        Else
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.None
        End If

        ' Start the timer to change the border style back to FixedSingle after a short delay
        borderStyleTimer.Interval = 100 ' 100 milliseconds (adjust as needed)
        borderStyleTimer.Start()
    End Sub

    Private WithEvents borderStyleTimer As New Timer()

    Private Sub borderStyleTimer_Tick(sender As Object, e As EventArgs) Handles borderStyleTimer.Tick
        ' Change the border style back to FixedSingle
        btnStop.BorderStyle = BorderStyle.FixedSingle

        ' Stop the timer
        borderStyleTimer.Stop()
    End Sub

    Private Sub frmMusic_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.None

        If isRepeating Then
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        Else
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.None
        End If

        If isPlaying Then
            btnPlayPause.BackColor = DefaultBackColor
            btnPlayPause.BorderStyle = BorderStyle.Fixed3D
        Else
            btnPlayPause.BackColor = DefaultBackColor
            btnPlayPause.BorderStyle = BorderStyle.None
        End If
    End Sub

    Private Sub btnRepeat_Click(sender As Object, e As EventArgs) Handles btnRepeat.Click
        btnRepeat.BackColor = Color.LightGray
        If isRepeating Then
            isRepeating = False
            btnRepeat.BorderStyle = BorderStyle.FixedSingle
        Else
            isRepeating = True
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        End If

        ' Update back colors for other buttons
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.None
        btnPlayPause.BorderStyle = If(isPlaying, BorderStyle.Fixed3D, BorderStyle.FixedSingle)
    End Sub

    Private Sub btnPlayPause_Click(sender As Object, e As EventArgs) Handles btnPlayPause.Click
        If waveOut IsNot Nothing Then
            If waveOut.PlaybackState = PlaybackState.Playing Then
                waveOut.Pause()
                btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))
                isPlaying = False
                btnPlayPause.BorderStyle = BorderStyle.FixedSingle
            ElseIf waveOut.PlaybackState = PlaybackState.Paused Then
                waveOut.Play()
                btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Pause.png"))
                isPlaying = True
                btnPlayPause.BorderStyle = BorderStyle.Fixed3D
            End If
        Else
            ' Start playing the selected track if audio is loaded for the first time
            Dim selectedRow As DataGridViewRow = dgSound.CurrentRow
            If selectedRow IsNot Nothing Then
                Dim fileNameWithoutExtension = selectedRow.Cells("Filename").Value.ToString()
                Dim fileType = selectedRow.Cells("Type").Value.ToString().ToLower()
                LoadAndPlayAudio(fileNameWithoutExtension, fileType)
                btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Pause.png"))
                isPlaying = True
                btnPlayPause.BorderStyle = BorderStyle.Fixed3D
            End If
        End If

        ' Update back colors for other buttons
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.None
        If isRepeating Then
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        Else
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.None
        End If
    End Sub

    Private Sub btnStop_MouseEnter(sender As Object, e As EventArgs) Handles btnStop.MouseEnter, btnPlayPause.MouseEnter, btnRepeat.MouseEnter
        Dim btn As PictureBox = CType(sender, PictureBox)
        btn.BackColor = Color.LightGray
        'If btn Is btnRepeat And isRepeating Then Exit Sub

        btn.BorderStyle = BorderStyle.FixedSingle
    End Sub

End Class

Can you make the columns on dgSound fit the control please? It's important that the column FileName is 60% the width.