Imports System.IO
Imports System.Text
Imports System.Timers
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.ZooFlow.Base
Imports DigitalData.Modules.Language.DateTimeEx

Namespace DocumentResultList
    Public Class Watcher
        Inherits BaseClass

        Private WithEvents FileOpenTimer As New Timer
        Private FileEx As Modules.Filesystem.File

        ' TODO: Hashes for checking if the opened file was modified externally
        Private HashOriginalFile As String = Nothing
        Private HashOpenedFile As String = Nothing
        Private Const FILE_OPEN_HANDLE_INTERVAL = 2000

        ''' <summary>
        ''' List of opened files containing the filepath that was opened and the document id
        ''' </summary>
        Private ReadOnly OpenFiles As New List(Of OpenFile)

        ''' <summary>
        ''' List of files that changed and are processed by the client, ie. updated
        ''' </summary>
        Private ReadOnly ProcessedFiles As New List(Of OpenFile)

        Public Event FileChanged As EventHandler(Of FileChangedArgs)

        Public Class OpenFile
            Public Document As Document
            Public ProcessId As Integer
            Public FilePath As String
            Public CurrentlyProcessing As Boolean = False
        End Class

        Public Class FileChangedArgs
            Public File As OpenFile
        End Class

        Public Sub New(pLogConfig As LogConfig)
            MyBase.New(pLogConfig)
            FileEx = New Modules.Filesystem.File(pLogConfig)
        End Sub

        Public Async Function OpenDocument(pDocument As Document) As Task(Of Boolean)
            Dim oResult As Tuple(Of Process, String) = Nothing

            If pDocument.FullPath IsNot Nothing AndAlso pDocument.FullPath.Trim <> String.Empty Then
                ' TODO: DONT put into openfiles
                oResult = OpenFileFromPath(pDocument)

            ElseIf pDocument.Extension IsNot Nothing AndAlso pDocument.Contents IsNot Nothing Then
                oResult = Await OpenFileFromByteArray(pDocument)

            End If

            If IsNothing(oResult) Then
                Logger.Warn("Process Id was empty. File [{0}] could not be opened.", pDocument.Id)
                Return False
            End If

            Dim oProcess = oResult.Item1
            Dim oFilePath = oResult.Item2

            Logger.Debug("File [{0}] opened with ProcessId [{1}]", oFilePath, oProcess.Id)

            OpenFiles.Add(New OpenFile With {
                .Document = pDocument,
                .FilePath = oFilePath,
                .ProcessId = oProcess.Id
            })


            If FileOpenTimer.Enabled = False Then
                FileOpenTimer.Interval = FILE_OPEN_HANDLE_INTERVAL
                FileOpenTimer.Start()
            End If

            Return True
        End Function

        Public Sub FileSaved(pOpenFile As OpenFile)
            OpenFiles.Remove(pOpenFile)
            ProcessedFiles.Remove(pOpenFile)
        End Sub

        Private Async Function OpenFileFromByteArray(pDocument As Document) As Task(Of Tuple(Of Process, String))
            Try
                Dim oTempPath = Path.Combine(Path.GetTempPath(), Constants.TEMP_PATH_SUBFOLDER)
                Dim oDirectory = Directory.CreateDirectory(oTempPath)
                Dim oFileName = $"{pDocument.Id}-{Now.UnixTimestamp}.{pDocument.Extension}"
                Dim oFilePath = Path.Combine(oTempPath, oFileName)

                Using oMemoryStream As New MemoryStream(pDocument.Contents)
                    Using oFileStream As New FileStream(oFilePath, FileMode.Create, FileAccess.Write)
                        Await oMemoryStream.CopyToAsync(oFileStream)
                    End Using
                End Using

                Dim oProcess = DoOpenFile(oFilePath)
                Return New Tuple(Of Process, String)(oProcess, oFilePath)

            Catch ex As Exception
                Logger.Error(ex)
                Return Nothing

            End Try
        End Function

        Private Function OpenFileFromPath(pDocument As Document) As Tuple(Of Process, String)
            Try
                Dim oProcess = DoOpenFile(pDocument.FullPath)
                Return New Tuple(Of Process, String)(oProcess, pDocument.FullPath)

            Catch ex As Exception
                Logger.Error(ex)
                Return Nothing
            End Try
        End Function

        Private Function DoOpenFile(pFilePath As String) As Process
            Dim oProcess = Process.Start(New ProcessStartInfo With {
                .FileName = pFilePath
            })

            Return oProcess
        End Function

        Private Sub FileOpenTimer_Elapsed() Handles FileOpenTimer.Elapsed
            Try
                For Each oOpenFile In OpenFiles

                    ' All files that are currently processe/updated on the outside, 
                    ' will not be checked again.
                    Dim oFileIsProcessed = ProcessedFiles.Contains(oOpenFile)
                    Debug.WriteLine($"File is processed: [{oFileIsProcessed}]")

                    If oFileIsProcessed = True Then
                        Continue For
                    End If

                    ' Check if the file is currently in use, and skip if it is.
                    Dim oIsLocked = FileEx.TestFileIsLocked(oOpenFile.FilePath)
                    Debug.WriteLine($"File is locked: [{oIsLocked}]")

                    If oIsLocked Then
                        Continue For
                    End If

                    ' If this point is reached, we assume the file was closed again after opening it.
                    ' ------

                    Debug.WriteLine($"File Closed")

                    ' Compute the current hash of the file and compare it with the one
                    ' in the database.
                    Dim oOldHash = oOpenFile.Document.FileHash
                    Dim oNewHash = FileEx.GetChecksum(oOpenFile.FilePath)

                    Debug.WriteLine($"Old Hash: [{oOldHash}]")
                    Debug.WriteLine($"New Hash: [{oNewHash}]")

                    ' If the the file did not change, remove it from the watch list
                    If oNewHash.Equals(oOldHash) Then
                        ProcessedFiles.Remove(oOpenFile)
                        OpenFiles.Remove(oOpenFile)
                        Continue For
                    End If

                    ' If this point is reached, we assume the file changed.
                    ' ------

                    Debug.WriteLine($"File Changed")

                    ' The File changed, so mark the file as being processed/updated 
                    ' and notify any listeners of the FileChanged event                    
                    ProcessedFiles.Add(oOpenFile)

                    RaiseEvent FileChanged(Me, New FileChangedArgs() With {.File = oOpenFile})
                Next
            Catch ex As Exception
                Logger.Error(ex)
            End Try
        End Sub
    End Class

End Namespace