Imports System.IO
Imports DigitalData.Modules.Base.FileWatcherFilters
Imports DigitalData.Modules.Logging


Public Class FileWatcher
    ' Internals
    Private ReadOnly _Logger As Logger
    Private ReadOnly _Watchers As List(Of FileSystemWatcher)
    Private ReadOnly _Files As Dictionary(Of String, FileWatcherProperties)
    Private ReadOnly _Filters As List(Of BaseFileFilter)

    ' Options
    Private _Path As String

    ' Public Events
    Public Event FileSaved(ByVal FullName As String, ByVal IsSpecial As Boolean)

    Public Sub New(LogConfig As LogConfig, Path As String, Optional Filters As List(Of BaseFileFilter) = Nothing)
        _Logger = LogConfig.GetLogger()
        _Files = New Dictionary(Of String, FileWatcherProperties)
        _Watchers = New List(Of FileSystemWatcher)
        _Filters = IIf(IsNothing(Filters), GetDefaultFilters(), Filters)
        _Path = Path

        For Each oFilePath In Directory.EnumerateFiles(_Path)
            Try
                If IO.File.Exists(oFilePath) Then
                    _Files.Add(oFilePath, New FileWatcherProperties With {
                        .CreatedAt = DateTime.Now,
                        .ChangedAt = Nothing
                    })
                End If
            Catch ex As Exception
                _Logger.Error(ex)
                _Logger.Warn("File {0} cannot be watched!")
            End Try
        Next
    End Sub

    Public Sub Add(Filter As String)
        _Watchers.Add(CreateWatcher(Filter))
    End Sub

    Public Sub Start()
        For Each oWatcher In _Watchers
            oWatcher.EnableRaisingEvents = True
        Next
    End Sub

    Public Sub [Stop]()
        For Each oWatcher In _Watchers
            If Not IsNothing(oWatcher) Then
                oWatcher.EnableRaisingEvents = False
                oWatcher.Dispose()
            End If
        Next
    End Sub

    Private Function GetDefaultFilters()
        Return New List(Of BaseFileFilter) From {
              New TempFileFilter,
              New OfficeFileFilter
        }
    End Function

    Private Function CreateWatcher(Filter As String)
        Dim oWatcher = New FileSystemWatcher() With {
            .Path = _Path,
            .Filter = Filter,
            .NotifyFilter = NotifyFilters.LastAccess _
                            Or NotifyFilters.LastWrite _
                            Or NotifyFilters.FileName _
                            Or NotifyFilters.Size _
                            Or NotifyFilters.FileName _
                            Or NotifyFilters.Attributes
        }

        AddHandler oWatcher.Created, AddressOf HandleFileCreated
        AddHandler oWatcher.Changed, AddressOf HandleFileChanged
        AddHandler oWatcher.Deleted, AddressOf HandleFileDeleted
        AddHandler oWatcher.Renamed, AddressOf HandleFileRenamed

        Return oWatcher
    End Function

    Private Sub HandleFileCreated(sender As Object, e As FileSystemEventArgs)
        _Files.Add(e.FullPath, New FileWatcherProperties())
        _Logger.Debug("[Created] " & e.FullPath)
    End Sub

    ''' <summary>
    ''' This may fire twice for a single save operation, 
    ''' see: https://blogs.msdn.microsoft.com/oldnewthing/20140507-00/?p=1053/
    ''' </summary>
    Private Sub HandleFileChanged(sender As Object, e As FileSystemEventArgs)
        _Files.Item(e.FullPath).ChangedAt = DateTime.Now
        _Logger.Debug("[Changed] " & e.FullPath)

        Dim oShouldRaiseSave As Boolean = Not _Filters.Any(Function(oFilter)
                                                               Return oFilter.ShouldFilter(e)
                                                           End Function)

        If oShouldRaiseSave Then
            RaiseEvent FileSaved(e.FullPath, False)
        End If
    End Sub
    Private Sub HandleFileDeleted(sender As Object, e As FileSystemEventArgs)
        _Files.Remove(e.FullPath)
        _Logger.Debug("[Removed] " & e.FullPath)
    End Sub

    Private Sub HandleFileRenamed(sender As Object, e As RenamedEventArgs)
        Dim oProperties = _Files.Item(e.OldFullPath)
        _Files.Remove(e.OldFullPath)
        _Files.Add(e.FullPath, oProperties)
        ' Soll eine umbenannte datei als NEU gelten?

        Dim oShouldRaiseSave = _Filters.Any(Function(oFilter)
                                                Return oFilter.ShouldRaiseSave(e)
                                            End Function)

        If oShouldRaiseSave Then
            RaiseEvent FileSaved(e.OldFullPath, True)
        End If

        _Logger.Debug("[Renamed] {0} --> {1}", e.OldFullPath, e.FullPath)
    End Sub


End Class
