Imports GBM.My.Resources
Imports System.IO

Public Class mgrBackup

    Private oSettings As mgrSettings
    Private bCancelOperation As Boolean

    Property Settings As mgrSettings
        Get
            Return oSettings
        End Get
        Set(value As mgrSettings)
            oSettings = value
        End Set
    End Property

    Property CancelOperation As Boolean
        Get
            Return bCancelOperation
        End Get
        Set(value As Boolean)
            bCancelOperation = value
        End Set
    End Property

    Public Event UpdateLog(sLogUpdate As String, bTrayUpdate As Boolean, objIcon As System.Windows.Forms.ToolTipIcon, bTimeStamp As Boolean)
    Public Event UpdateBackupInfo(oGame As clsGame)
    Public Event SetLastAction(sMessage As String)

    Public Function CheckForUtilities(ByVal strPath As String) As Boolean
        If File.Exists(strPath) Then
            Return True
        Else
            Return False
        End If
    End Function

    Private Function DoManifestUpdate(ByVal oGameInfo As clsGame, ByVal sBackupFile As String, ByVal dTimeStamp As DateTime, ByVal sCheckSum As String) As Boolean
        Dim oItem As New clsBackup

        'Create manifest item
        oItem.MonitorID = oGameInfo.ID
        'Keep the path relative to the manifest location
        oItem.FileName = sBackupFile.Replace(Settings.BackupFolder & Path.DirectorySeparatorChar, String.Empty)
        oItem.DateUpdated = dTimeStamp
        oItem.UpdatedBy = My.Computer.Name
        oItem.CheckSum = sCheckSum

        'Save Remote Manifest
        If Not oGameInfo.AppendTimeStamp Then
            If Not mgrManifest.DoUpdateLatestManifest(oItem, mgrSQLite.Database.Remote) Then
                mgrManifest.DoManifestAdd(oItem, mgrSQLite.Database.Remote)
            End If
        Else
                mgrManifest.DoManifestAdd(oItem, mgrSQLite.Database.Remote)
        End If

        'Save Local Manifest
        If mgrManifest.DoManifestCheck(oItem.MonitorID, mgrSQLite.Database.Local) Then
            mgrManifest.DoManifestUpdateByMonitorID(oItem, mgrSQLite.Database.Local)
        Else
            mgrManifest.DoManifestAdd(oItem, mgrSQLite.Database.Local)
        End If

        Return True
    End Function

    Private Sub BuildFileList(ByVal sList As String, ByVal sPath As String)
        Dim oStream As StreamWriter

        Try
            If File.Exists(sPath) Then File.Delete(sPath)
            oStream = New StreamWriter(sPath)
            Using oStream
                If sList <> String.Empty Then
                    For Each sTypeItem As String In sList.Split(":")
                        oStream.WriteLine("""" & sTypeItem & """")
                    Next
                End If
                oStream.Flush()
            End Using
        Catch ex As Exception
            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorFileList, ex.Message), False, ToolTipIcon.Error, True)
        End Try
    End Sub

    Private Function VerifySavePath(ByVal oGame As clsGame) As String
        Dim sSavePath As String

        If oGame.AbsolutePath = False Then
            If oGame.Path <> String.Empty Then
                sSavePath = oGame.ProcessPath & Path.DirectorySeparatorChar & oGame.Path
            Else
                sSavePath = oGame.ProcessPath
            End If
        Else
            sSavePath = oGame.Path
        End If

        Return sSavePath
    End Function

    Private Function GetFileName(ByVal oGame As clsGame) As String
        Dim sName As String

        If oSettings.UseGameID Then
            sName = oGame.ID
        Else
            sName = oGame.FileSafeName
        End If

        Return sName
    End Function

    Public Function CheckBackupPrereq(ByVal oGame As clsGame) As Boolean
        Dim sBackupFile As String = oSettings.BackupFolder
        Dim sSavePath As String
        Dim sOverwriteMessage As String
        Dim lAvailableSpace As Long
        Dim lFolderSize As Long

        If oSettings.CreateSubFolder Then sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame)
        sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame) & ".7z"

        'Verify saved game path
        sSavePath = VerifySavePath(oGame)

        'Calculate space
        lAvailableSpace = mgrCommon.GetAvailableDiskSpace(oSettings.BackupFolder)
        lFolderSize = mgrCommon.GetFolderSize(sSavePath, oGame.IncludeArray, oGame.ExcludeArray)

        'Show Available Space
        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrCommon_AvailableDiskSpace, mgrCommon.FormatDiskSpace(lAvailableSpace)), False, ToolTipIcon.Info, True)

        'Show Save Folder Size
        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrCommon_SavedGameFolderSize, New String() {oGame.Name, mgrCommon.FormatDiskSpace(lFolderSize)}), False, ToolTipIcon.Info, True)

        If lFolderSize >= lAvailableSpace Then
            If mgrCommon.ShowMessage(mgrBackup_ConfirmDiskSpace, MsgBoxStyle.YesNo) = MsgBoxResult.No Then
                RaiseEvent UpdateLog(mgrBackup_ErrorDiskSpace, False, ToolTipIcon.Error, True)
                Return False
            End If
        End If

        'A manifest check is only required when "Save Multiple Backups" is disabled
        If Not oGame.AppendTimeStamp Then
            If mgrRestore.CheckManifest(oGame.ID) Then
                If mgrCommon.ShowMessage(mgrBackup_ConfirmManifestConflict, oGame.Name, MsgBoxStyle.YesNo) = MsgBoxResult.No Then
                    RaiseEvent UpdateLog(mgrBackup_ErrorManifestConflict, False, ToolTipIcon.Error, True)
                    Return False
                End If
            End If
        End If

        If oSettings.ShowOverwriteWarning And File.Exists(sBackupFile) And Not oGame.AppendTimeStamp Then
            If oGame.AbsolutePath Then
                sOverwriteMessage = mgrBackup_ConfirmOverwrite
            Else
                sOverwriteMessage = mgrBackup_ConfirmOverwriteRelative
            End If

            If mgrCommon.ShowMessage(sOverwriteMessage, MsgBoxStyle.YesNo) = MsgBoxResult.No Then
                RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorOverwriteAbort, oGame.Name), False, ToolTipIcon.Error, True)
                Return False
            End If
        End If

            Return True
    End Function

    Private Sub CheckOldBackups(ByVal oGame As clsGame)
        Dim oGameBackups As List(Of clsBackup) = mgrManifest.DoManifestGetByMonitorID(oGame.ID, mgrSQLite.Database.Remote)
        Dim oGameBackup As clsBackup
        Dim sOldBackup As String
        Dim iBackupCount As Integer = oGameBackups.Count
        Dim iDelCount As Integer

        'If we've hit or exceeded the maximum backup limit
        If oGameBackups.Count >= oGame.BackupLimit Then
            'How many do we need to delete
            iDelCount = (oGameBackups.Count - oGame.BackupLimit) + 1

            'Delete the oldest backup(s) (Manifest entry and backup file)
            For i = 1 To iDelCount
                oGameBackup = oGameBackups(oGameBackups.Count - i)
                sOldBackup = Settings.BackupFolder & Path.DirectorySeparatorChar & oGameBackup.FileName

                mgrManifest.DoManifestDeleteByManifestID(oGameBackup, mgrSQLite.Database.Remote)
                mgrManifest.DoManifestDeleteByManifestID(oGameBackup, mgrSQLite.Database.Local)
                mgrCommon.DeleteFile(sOldBackup)
                mgrCommon.DeleteDirectoryByBackup(Settings.BackupFolder & Path.DirectorySeparatorChar, oGameBackup)

                RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_BackupLimitExceeded, Path.GetFileName(sOldBackup)), False, ToolTipIcon.Info, True)
            Next
        End If
    End Sub

    Private Function BuildFileTimeStamp(ByVal dDate As Date) As String
        Return " " & dDate.Month & "-" & dDate.Day & "-" & dDate.Year & "-" & dDate.Hour & "-" & dDate.Minute & "-" & dDate.Second
    End Function

    Private Function HandleSubFolder(ByVal oGame As clsGame, ByVal sPath As String) As Boolean
        Try
            If Not Directory.Exists(sPath) Then
                Directory.CreateDirectory(sPath)
            End If
        Catch ex As Exception
            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorSubFolderCreate, New String() {oGame.Name, ex.Message}), False, ToolTipIcon.Error, True)
            Return False
        End Try

        Return True
    End Function

    Public Sub ImportBackupFiles(ByVal hshImportList As Hashtable)
        Dim oGame As clsGame
        Dim bOverwriteCurrent As Boolean = False
        Dim bContinue As Boolean = True
        Dim sFileToImport As String
        Dim sBackupFile As String
        Dim oBackup As clsBackup

        For Each de As DictionaryEntry In hshImportList
            sFileToImport = CStr(de.Key)
            oGame = DirectCast(de.Value, clsGame)

            'Enter overwite mode if we are importing a single backup and "Save Multiple Backups" is not enabled.
            If hshImportList.Count = 1 And Not oGame.AppendTimeStamp Then bOverwriteCurrent = True

            If File.Exists(sFileToImport) Then
                sBackupFile = oSettings.BackupFolder

                If oSettings.CreateSubFolder Then
                    sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame)
                    bContinue = HandleSubFolder(oGame, sBackupFile)
                End If

                If bContinue Then
                    oBackup = New clsBackup
                    oBackup.MonitorID = oGame.ID
                    oBackup.DateUpdated = File.GetLastWriteTime(sFileToImport)
                    oBackup.UpdatedBy = mgrBackup_ImportedFile
                    If bOverwriteCurrent Then
                        sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame) & ".7z"
                    Else
                        sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame) & BuildFileTimeStamp(oBackup.DateUpdated) & ".7z"
                    End If

                    oBackup.FileName = sBackupFile.Replace(Settings.BackupFolder & Path.DirectorySeparatorChar, String.Empty)

                    If bOverwriteCurrent Then
                        If mgrCommon.CopyFile(sFileToImport, sBackupFile, True) Then
                            oBackup.CheckSum = mgrHash.Generate_SHA256_Hash(sBackupFile)
                            If Not mgrManifest.DoUpdateLatestManifest(oBackup, mgrSQLite.Database.Remote) Then
                                mgrManifest.DoManifestAdd(oBackup, mgrSQLite.Database.Remote)
                            End If
                            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ImportSuccess, New String() {sFileToImport, oGame.Name}), False, ToolTipIcon.Info, True)
                        Else
                            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorImportBackupCopy, sFileToImport), False, ToolTipIcon.Error, True)
                        End If
                    Else
                        If mgrCommon.CopyFile(sFileToImport, sBackupFile, False) Then
                            oBackup.CheckSum = mgrHash.Generate_SHA256_Hash(sBackupFile)
                            mgrManifest.DoManifestAdd(oBackup, mgrSQLite.Database.Remote)
                            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ImportSuccess, New String() {sFileToImport, oGame.Name}), False, ToolTipIcon.Info, True)
                        Else
                            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorImportBackupCopy, sFileToImport), False, ToolTipIcon.Error, True)
                        End If
                    End If
                End If
            End If
        Next
    End Sub

    Public Sub DoBackup(ByVal oBackupList As List(Of clsGame))
        Dim oGame As clsGame
        Dim bDoBackup As Boolean
        Dim bBackupCompleted As Boolean
        Dim prs7z As Process
        Dim sBackupFile As String
        Dim sSavePath As String
        Dim dTimeStamp As DateTime
        Dim sTimeStamp As String
        Dim sHash As String

        For Each oGame In oBackupList
            'Init
            prs7z = New Process
            sBackupFile = oSettings.BackupFolder
            sSavePath = String.Empty
            dTimeStamp = Date.Now
            sTimeStamp = BuildFileTimeStamp(dTimeStamp)
            sHash = String.Empty
            bDoBackup = True
            bBackupCompleted = False
            CancelOperation = False
            RaiseEvent UpdateBackupInfo(oGame)

            If oSettings.CreateSubFolder Then
                sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame)
                bDoBackup = HandleSubFolder(oGame, sBackupFile)
            End If

            If oGame.AppendTimeStamp Then
                If oGame.BackupLimit > 0 Then CheckOldBackups(oGame)
                sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame) & sTimeStamp & ".7z"
            Else
                sBackupFile = sBackupFile & Path.DirectorySeparatorChar & GetFileName(oGame) & ".7z"
            End If

            If bDoBackup Then

                sSavePath = VerifySavePath(oGame)

                If oGame.FolderSave = True Then
                    BuildFileList("*", mgrPath.IncludeFileLocation)
                Else
                    BuildFileList(oGame.FileType, mgrPath.IncludeFileLocation)
                End If

                BuildFileList(oGame.ExcludeList, mgrPath.ExcludeFileLocation)

                Try
                    If Directory.Exists(sSavePath) Then
                        If Settings.Is7zUtilityValid Then
                            'Need to delete any prior archive if it exists, the 7za utility does not support overwriting or deleting existing archives.
                            'If we let 7za update existing archives it will lead to excessive bloat with games that routinely add and remove files with many different file names.
                            If File.Exists(sBackupFile) Then
                                File.Delete(sBackupFile)
                            End If

                            prs7z.StartInfo.Arguments = "a" & oSettings.Prepared7zArguments & "-t7z -mx" & oSettings.CompressionLevel & " -i@""" & mgrPath.IncludeFileLocation & """ -x@""" & mgrPath.ExcludeFileLocation & """ """ & sBackupFile & """ -r"
                            prs7z.StartInfo.FileName = oSettings.Utility7zLocation
                            prs7z.StartInfo.WorkingDirectory = sSavePath
                            prs7z.StartInfo.UseShellExecute = False
                            prs7z.StartInfo.RedirectStandardOutput = True
                            prs7z.StartInfo.CreateNoWindow = True
                            prs7z.Start()
                            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_BackupInProgress, sSavePath), False, ToolTipIcon.Info, True)
                            While Not prs7z.StandardOutput.EndOfStream
                                If CancelOperation Then
                                    prs7z.Kill()
                                    RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorFullAbort, oGame.Name), True, ToolTipIcon.Error, True)
                                    Exit While
                                End If
                                RaiseEvent UpdateLog(prs7z.StandardOutput.ReadLine, False, ToolTipIcon.Info, False)
                            End While
                            prs7z.WaitForExit()
                            If Not CancelOperation Then
                                Select Case prs7z.ExitCode
                                    Case 0
                                        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_BackupComplete, New String() {oGame.Name, mgrCommon.FormatDiskSpace(mgrCommon.GetFileSize(sBackupFile))}), False, ToolTipIcon.Info, True)
                                        bBackupCompleted = True
                                    Case 1
                                        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_7zWarnings, oGame.Name), True, ToolTipIcon.Warning, True)
                                        bBackupCompleted = True
                                    Case 2
                                        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_7zFatalError, oGame.Name), True, ToolTipIcon.Error, True)
                                        bBackupCompleted = False
                                    Case 7
                                        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_7zCommandFailure, oGame.Name), True, ToolTipIcon.Error, True)
                                        bBackupCompleted = False
                                End Select
                            End If
                            prs7z.Dispose()
                        Else
                            RaiseEvent UpdateLog(App_Invalid7zDetected, True, ToolTipIcon.Error, True)
                            bBackupCompleted = False
                        End If
                    Else
                        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorNoSavePath, oGame.Name), True, ToolTipIcon.Error, True)
                        bBackupCompleted = False
                    End If

                    'Write Main Manifest
                    If bBackupCompleted Then

                        'Generate checksum for new backup
                        RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_GenerateHash, oGame.Name), False, ToolTipIcon.Info, True)
                        sHash = mgrHash.Generate_SHA256_Hash(sBackupFile)

                        If Not DoManifestUpdate(oGame, sBackupFile, dTimeStamp, sHash) Then
                            RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorManifestFailure, oGame.Name), True, ToolTipIcon.Error, True)
                        End If

                        'Write the process path if we have it
                        If oGame.AbsolutePath = False Then
                            mgrMonitorList.DoListUpdate(oGame)
                        End If
                    End If
                Catch ex As Exception
                    RaiseEvent UpdateLog(mgrCommon.FormatString(mgrBackup_ErrorOtherFailure, New String() {oGame.Name, ex.Message}), False, ToolTipIcon.Error, True)
                End Try
            End If

            If bBackupCompleted Then
                RaiseEvent SetLastAction(mgrCommon.FormatString(mgrBackup_ActionComplete, oGame.CroppedName))
            Else
                RaiseEvent SetLastAction(mgrCommon.FormatString(mgrBackup_ActionFailed, oGame.CroppedName))
            End If
        Next
    End Sub

End Class
