' *
' * The project site is at: http://sourceforge.jp/projects/fishbornas/
' *
' * First author tiritomato 2012.
' *
' * Distributed under the FishbornArchiveShelf License (See
' *  file "Licenses/License.txt" contained in a project, or the following link.
' *  http://sourceforge.jp/projects/fishbornas/scm/svn/blobs/head/trunk/Licenses/License.txt)
' *
' * 2012.06.07 Initial Revision (tiritomato)
' *

Partial Public Class AppBase
    Partial Public Class Archive
        Partial Public Class Threading

            Public Class ScanTask
                Implements IAppBase
                Implements Logic.Threading.TaskQueueingThread.ITask
                Public Property AppBase As AppBase Implements IAppBase.AppBase
                Public Path() As String
                Public Ret As Collections.Generic.List(Of ArchiveItemScanArgs)

                Public Sub New(ByVal AppBase As AppBase, ByVal Path As Collections.ObjectModel.ReadOnlyCollection(Of String))
                    Me.AppBase = AppBase
                    ReDim Me.Path(Path.Count - 1)
                    Path.CopyTo(Me.Path, 0)
                End Sub

                Public Sub Task(ByVal SenderIsStopRequested As Logic.Threading.SignalHandler) Implements Logic.Threading.TaskQueueingThread.ITask.Task

                    Ret = New Collections.Generic.List(Of ArchiveItemScanArgs)

                    If (AppBase Is Nothing Or Path Is Nothing) OrElse Path.Length <= 0 Then Return

                    AppBase.Echoing.TaskStartEcho("Scan")

                    If AppBase.Config.Locations.WorkFolder.IsReady = False Then
                        AppBase.Echoing.TaskExitedEcho("Temporary Folder Error")
                        Return
                    End If

                    AppBase.Echoing.Log.Echo("Prescaning", AppendLogArgs.LogType.Status)

                    For idx As Integer = 0 To Path.Length - 1
                        If String.IsNullOrWhiteSpace(Path(idx)) Then Continue For
                        Ret.Add(FileWorkCreatePrescanedArgs(Path(idx)))
                    Next

                    For Each destArgs As ArchiveItemScanArgs In Ret
                        For Each srcArgs As ArchiveItemScanArgs In Ret
                            If (destArgs IsNot srcArgs) AndAlso
                                (srcArgs.Data.DeleteFiles IsNot Nothing) AndAlso
                                (srcArgs.Data.DeleteFiles.Contains(destArgs.Path)) Then
                                destArgs.Data.IsDelete = True
                                Exit For
                            End If
                        Next
                    Next

                    If SenderIsStopRequested IsNot Nothing AndAlso SenderIsStopRequested() Then
                        AppBase.Echoing.TaskExitedEcho("User Cancel Exited")
                        Return
                    End If

                    If 0 < AppBase.Config.ExportableGroup.ArchiveTaskPresets.Count Then

                        AppBase.Echoing.Log.Echo("Scaning", AppendLogArgs.LogType.Status)
                        Dim ctProg As Integer = 0
                        For Each destArgs As ArchiveItemScanArgs In Ret
                            Dim isContinue As Boolean = Not DoEachScanIsCriticalError(destArgs, SenderIsStopRequested)
                            AppBase.Echoing.ArchiveItemScan.Echo(destArgs)
                            If isContinue = False Then Exit For
                            ctProg += 1
                            AppBase.Echoing.Progress.Echo(ctProg, Ret.Count)
                        Next

                    End If

                    AppBase.Echoing.TaskCompletedEcho()

                End Sub

                Private Function FileWorkCreatePrescanedArgs(ByVal SrcPath As Logic.FileSystem.Path) As ArchiveItemScanArgs

                    Dim ret = New ArchiveItemScanArgs(SrcPath)
                    ret.Data.FileInformationFlush(ret.Path)

                    If ret.Data.FileExists = False Then Return ret

                    Dim Config As ArchiveTaskOptionConfig = AppBase.ArchiveTaskConfig

                    ' filter check ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                    If (Config.FilterOption.HiddenFile And ret.FileHidden) Or
                        (Config.FilterOption.HiddenDirectory And ret.DirHidden) Then ret.Data.IsFilter = True

                    ' filter delete ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                    If ret.Data.IsFilter And Config.FilterOption.DeleteMirroring Then

                        Dim ParseInf As New CommandInfo
                        ParseInf.Name = SrcPath.NameWithoutExtention.ToString

                        For Each Preset As AppBase.ArchiveTaskPreset In AppBase.ArchiveTaskPresets

                            If Preset Is Nothing OrElse Preset.IsFilteredDelete = False Then Continue For

                            Dim RenameFileName As Logic.FileSystem.Path
                            Try
                                RenameFileName = New Uri(New Uri(SrcPath.ToString), ParseInf.Parse(Preset.RenamePolicy).ToString.Trim).LocalPath
                            Catch ex As Exception
                                RenameFileName = String.Empty
                            End Try

                            If RenameFileName.IsNullOrWhiteSpace = False And RenameFileName.HasExtention = False Then
                                Dim CompressSetting As ArchiveOptionConfigCollection = Preset.CompressSetting
                                If CompressSetting Is Nothing Then CompressSetting = AppBase.DefaultCompressSetting
                                RenameFileName.Extention = CompressSetting.SelectedExt.Extention
                            End If

                            If RenameFileName.FileExists = False OrElse (Config.CreateOption.IsOriginalArcProtection And RenameFileName.Equals(SrcPath)) Then Continue For

                            ret.Data.DeleteFiles.Add(RenameFileName.ToString)

                        Next

                    End If

                    Return ret

                End Function

                Private Function DoEachScanIsCriticalError(ByVal srcArgs As ArchiveItemScanArgs, ByVal IsStopRequested As Logic.Threading.SignalHandler) As Boolean

                    Const ScanFilterMask As PDTASK = _
                        PDTASK.DELETE Or _
                        PDTASK.ERR_FILENOTEXISTS Or _
                        PDTASK.FILTERED

                    Const ScanResultMask_BreakingFaultFlag As PDTASK = _
                        PDTASK.ERR_SCANCMD_INVALIDEXIT Or _
                        PDTASK.ERR_SCANCMD Or _
                        PDTASK.USERCANCEL

                    If (srcArgs.Data.Task And ScanFilterMask) <> PDTASK.NONE Then Return False ' do nothing and continuable

                    AppBase.Echoing.Log.Echo(srcArgs.Path.ToString, AppendLogArgs.LogType.Status)

                    Dim ParseInf As New CommandInfo
                    ParseInf.Name = srcArgs.Path.NameWithoutExtention.ToString

                    For idx As Integer = 0 To AppBase.Config.ExportableGroup.ArchiveTaskPresets.Count - 1

                        If IsStopRequested IsNot Nothing AndAlso IsStopRequested() = True Then
                            srcArgs.Data.IsUserCanceled = True
                            Exit For
                        End If

                        Dim Preset As AppBase.ArchiveTaskPreset = AppBase.Config.ExportableGroup.ArchiveTaskPresets(idx)
                        Select Case DoEachPresetScan(srcArgs, ParseInf, Preset, IsStopRequested)
                            Case EachPresetScanResult.Break
                                Exit For
                            Case EachPresetScanResult.Skip, EachPresetScanResult.OriginalOverwriteProtectionSkip, EachPresetScanResult.MirrorOverwriteProtectionSkip
                                ' do nothing and try next preset
                            Case Else
                                ' error trap
                                Throw New ApplicationException
                        End Select

                    Next

                    Return ((srcArgs.Data.Task And ScanResultMask_BreakingFaultFlag) <> PDTASK.NONE)

                End Function

                Private Enum EachPresetScanResult
                    Break
                    Skip
                    OriginalOverwriteProtectionSkip
                    MirrorOverwriteProtectionSkip
                End Enum

                Private Function DoEachPresetScan(ByVal srcArgs As ArchiveItemScanArgs, ParseInf As CommandInfo, Preset As AppBase.ArchiveTaskPreset, ByVal IsStopRequested As Logic.Threading.SignalHandler) As EachPresetScanResult

                    If Preset Is Nothing OrElse Preset.ArchiveExtentionList.Contains(srcArgs.Path.Extention) = False Then Return EachPresetScanResult.Skip

                    Dim CompressSetting As ArchiveOptionConfigCollection = Preset.CompressSetting
                    If CompressSetting Is Nothing Then CompressSetting = AppBase.DefaultCompressSetting

                    ' Check ExistedPath ProtectionSkip Setting
                    Dim RenameFileName As Logic.FileSystem.Path
                    Try
                        RenameFileName = New Uri(New Uri(srcArgs.Path.ToString), ParseInf.Parse(Preset.RenamePolicy).ToString.Trim).LocalPath
                    Catch ex As Exception
                        RenameFileName = String.Empty
                    End Try

                    If RenameFileName.IsNullOrWhiteSpace = False Then

                        ' Combine Compress Extention and Complete New Mirror File Path
                        Dim CreateMirrorPath As New Logic.FileSystem.Path(RenameFileName.ToString)
                        If CreateMirrorPath.HasExtention = False Then CreateMirrorPath.Extention = CompressSetting.SelectedExt.Extention
                        srcArgs.Data.MirrorArchivePath = CreateMirrorPath.ToString

                        ' Original Archive Overwrite Protection
                        If AppBase.ArchiveTaskConfig.CreateOption.IsOriginalArcProtection And srcArgs.Path.Equals(CreateMirrorPath) Then
                            AppBase.Echoing.Log.Echo(String.Format("[{0}] Original Archive Protection Skip. {1}", Preset.Name, CreateMirrorPath))
                            Return EachPresetScanResult.OriginalOverwriteProtectionSkip
                        End If

                        ' Old Mirror File Overwrite Setting Check
                        If CreateMirrorPath.FileExists Then

                            Dim ProtectionSkip As Boolean = False

                            Select Case AppBase.ArchiveTaskConfig.CreateOption.Overwrite
                                Case AppBase.ArchiveTaskOptionConfig.CreateOptionConfig.OverwriteMode.ModifiedNewerThanCreatedWrite
                                    Try
                                        ProtectionSkip = (srcArgs.Data.LastWriteTime <= New IO.FileInfo(CreateMirrorPath.ToString).LastWriteTime)
                                    Catch ex As Exception
#If DEBUG Then
                                        Throw
#End If
                                        ProtectionSkip = True
                                        Return EachPresetScanResult.Skip
                                    End Try
                                Case AppBase.ArchiveTaskOptionConfig.CreateOptionConfig.OverwriteMode.NeverWrite
                                    ProtectionSkip = True
                            End Select

                            If ProtectionSkip Then
                                AppBase.Echoing.Log.Echo(String.Format("[{0}] Old Mirror Archive Protection Skip. {1}", Preset.Name, CreateMirrorPath))
                                Return EachPresetScanResult.MirrorOverwriteProtectionSkip
                            End If

                        End If

                    End If

                    Dim Builder As New System.Text.StringBuilder
                    If Not String.IsNullOrWhiteSpace(Preset.ScanCmd) Then
                        Dim Reader As New IO.StringReader(Preset.ScanCmd)
                        Do
                            Dim LineOfCmd As String = Reader.ReadLine
                            If LineOfCmd Is Nothing Then Exit Do
                            If String.IsNullOrWhiteSpace(LineOfCmd) Then Continue Do
                            Dim CmdInf As New CommandInfo(AppBase)
                            CmdInf.ArcPath = srcArgs.Path.ToString
                            Dim ParseCmd As CommandInfo.ParseResult = CmdInf.Parse(LineOfCmd)
                            If Not String.IsNullOrWhiteSpace(ParseCmd.Result.Cmd) Then
                                Dim RebuildLine As New Collections.Generic.List(Of String)
                                RebuildLine.Add(ParseCmd.Result.Cmd)
                                If ParseCmd.IsCreateWindow Then RebuildLine.Add("%CreateWindow%")
                                If ParseCmd.IsIgnoreStdOut Then RebuildLine.Add("%IgnoreStdOut%")
                                Builder.AppendLine(String.Join(" ", RebuildLine) & ChrW(0))
                            End If
                        Loop
                    End If
                    Dim ScanCmdListBlock As String = Builder.ToString

                    Builder.Clear()
                    If Not String.IsNullOrWhiteSpace(Preset.UserCmd) Then
                        Dim Reader As New IO.StringReader(Preset.ScanCmd)
                        Do
                            Dim LineOfCmd As String = Reader.ReadLine
                            If LineOfCmd Is Nothing Then Exit Do
                            If Not String.IsNullOrWhiteSpace(LineOfCmd) Then Builder.AppendLine(LineOfCmd)
                        Loop
                    End If
                    Dim UserCmdListBlock As String = Builder.ToString

                    If String.IsNullOrWhiteSpace(ScanCmdListBlock) Then

                        If String.IsNullOrWhiteSpace(UserCmdListBlock) And Preset.IsExtract = False And Preset.IsCompress = False Then _
                            Return EachPresetScanResult.Skip

                        srcArgs.Data.PresetName = Preset.Name
                        srcArgs.Data.IsUserCmd = True
                        Return EachPresetScanResult.Break

                    End If

                    Dim MMF As IO.MemoryMappedFiles.MemoryMappedFile = Nothing
                    Try
                        MMF = IO.MemoryMappedFiles.MemoryMappedFile.CreateNew(AppBase.PipeStarter.SharingMemoryMappedFile.CommandListMapName, System.Text.Encoding.Unicode.GetBytes(ScanCmdListBlock).Length)
                        Using Stream As IO.MemoryMappedFiles.MemoryMappedViewStream = MMF.CreateViewStream()
                            Using Writer As New IO.StreamWriter(Stream, System.Text.Encoding.Unicode)
                                Writer.Write(ScanCmdListBlock)
                            End Using
                        End Using
                        Dim p As New Process
                        p.StartInfo.FileName = "Hameln.exe"
                        p.StartInfo.Arguments = String.Format("{0}{1}",
                                                              Logic.CommandlineParser.Arguments.DASHCODE,
                                                              AppBase.PipeStarter.ModeArgumentParameterSet(AppBase.PipeStarter.Mode.ScanCommandList).Code)
                        p.Start()

                        Dim CatchFlag As System.Threading.Mutex = Nothing
                        Dim CompleteFlagRef As System.Threading.Mutex = Nothing
                        Try
                            Dim CatchBuffer As New Collections.Generic.List(Of String)
                            CompleteFlagRef = Logic.Threading.PallingWaitMutexExist(AppBase.PipeStarter.SharingMemoryMappedFile.CommandListConsoleBufferWriteCompleteFlagName, 10, , Function() p.HasExited)
                            If CompleteFlagRef IsNot Nothing Then

                                CatchBuffer.Clear()
                                Using CatchMMF As IO.MemoryMappedFiles.MemoryMappedFile = IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(AppBase.PipeStarter.SharingMemoryMappedFile.CommandListConsoleBufferMapName)
                                    Using Stream As IO.MemoryMappedFiles.MemoryMappedViewStream = CatchMMF.CreateViewStream()
                                        Using CatchReader As New IO.StreamReader(Stream, System.Text.Encoding.Unicode)
                                            Do
                                                Dim Line As String = CatchReader.ReadLine
                                                If Line Is Nothing Then Exit Do
                                                Line = Line.Split(New Char() {ChrW(0)})(0)
                                                If Not String.IsNullOrWhiteSpace(Line) Then CatchBuffer.Add(Line)
                                            Loop
                                        End Using
                                    End Using
                                End Using

                                CatchFlag = Logic.Threading.CreateOrWaitMutex(AppBase.PipeStarter.SharingMemoryMappedFile.CommandListConsoleBufferWriteCompleteCatchFlagName)
                                CompleteFlagRef.WaitOne()
                                CompleteFlagRef.ReleaseMutex()
                                CompleteFlagRef.Dispose()
                                CompleteFlagRef = Nothing
                                CatchFlag.ReleaseMutex()
                                CatchFlag.Dispose()
                                CatchFlag = Nothing

                            End If

                            p.WaitForExit()

                            For Each OutputLine As String In CatchBuffer
                                AppBase.Echoing.Log.Echo(OutputLine, AppendLogArgs.LogType.ConsoleOut)
                            Next

                            Select Case p.ExitCode
                                Case Is < 0
                                    srcArgs.Data.IsErrScanCmdInvalidExit = True
                                    Return EachPresetScanResult.Break
                                Case AppBase.RESULT.OK
                                    ' do nothing, continue for and check next preset
                                    Return EachPresetScanResult.Skip
                                Case Else
                                    srcArgs.Data.PresetName = Preset.Name
                                    srcArgs.Data.IsUserCmd = True
                                    Return EachPresetScanResult.Break
                            End Select

                        Catch ex As Exception
                            srcArgs.Data.IsErrScanCmd = True
                            Return EachPresetScanResult.Break

                        Finally
                            If CompleteFlagRef IsNot Nothing Then CompleteFlagRef.Dispose()
                            If CatchFlag IsNot Nothing Then CatchFlag.Dispose()
                        End Try

                    Catch ex As Exception
#If DEBUG Then
                        Throw
#End If
                    Finally
                        If MMF IsNot Nothing Then MMF.Dispose()
                    End Try

                    ' ??? Error Return(this returning is critical error path)
#If DEBUG Then
                    Throw New ApplicationException
#End If
                    Return EachPresetScanResult.Break

                End Function

            End Class

        End Class ' AppBase.Archive.Threading
    End Class ' AppBase.Archive
End Class ' AppBase
