Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Specialized
Imports System.Diagnostics
Imports System.IO
Imports System.Windows.Forms
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Composition
Imports Nexamas.UI.FileSystem
Imports Nexamas.UI.General
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports Nexamas.UI.Layout
Imports SkiaSharp

Namespace Nexamas.UI.Components


    Partial Public NotInheritable Class MASFilePickerControl
#Region "Browser context menu commands"

        Friend Sub ExecuteItemContextMenuCommand(commandId As String,
                                                 entry As FileSystemEntry)
            If _disposedLocal OrElse Not Me.Enabled Then Return
            If entry Is Nothing Then Return

            Dim id As String = If(commandId, String.Empty).Trim()
            If id.Length = 0 Then Return

            Select Case id
                Case MASFilePickerContextMenuCommands.Open
                    OpenEntry(entry)

                Case MASFilePickerContextMenuCommands.OpenInWindows
                    OpenEntryInWindowsShell(entry)

                Case MASFilePickerContextMenuCommands.Copy
                    CopyEntryToClipboard(entry, cut:=False)

                Case MASFilePickerContextMenuCommands.Cut
                    CopyEntryToClipboard(entry, cut:=True)

                Case MASFilePickerContextMenuCommands.CopyPath
                    CopyEntryPathToClipboard(entry)

                Case MASFilePickerContextMenuCommands.Delete
                    DeleteEntryToRecycleBin(entry)

                Case MASFilePickerContextMenuCommands.Properties
                    ShowEntryProperties(entry)

                Case MASFilePickerContextMenuCommands.Rename
                    BeginInlineRenameEntry(entry)
            End Select
        End Sub

        Friend Sub ExecuteBackgroundContextMenuCommand(commandId As String)
            If _disposedLocal OrElse Not Me.Enabled Then Return

            Dim id As String = If(commandId, String.Empty).Trim()
            If id.Length = 0 Then Return

            Select Case id
                Case MASFilePickerContextMenuCommands.Paste
                    PasteClipboardIntoCurrentFolder()

                Case MASFilePickerContextMenuCommands.Refresh
                    RefreshItems()
            End Select
        End Sub

        Friend Function BeginInlineRenameEntry(entry As FileSystemEntry) As Boolean
            If _disposedLocal OrElse Not Me.Enabled Then Return False
            If entry Is Nothing Then Return False
            If _browser Is Nothing Then Return False
            If Not CanModifyPhysicalEntry(entry) Then Return False

            SelectBrowserEntry(entry)
            Return _browser.BeginInlineRenameEntry(entry)
        End Function

        Friend Function TryRenameEntry(entry As FileSystemEntry,
                                       newName As String,
                                       ByRef errorMessage As String) As Boolean
            errorMessage = String.Empty

            If _disposedLocal OrElse Not Me.Enabled Then
                errorMessage = "File Picker is not available."
                Return False
            End If

            If entry Is Nothing Then
                errorMessage = "No item was selected for rename."
                Return False
            End If

            If Not CanModifyPhysicalEntry(entry) Then
                errorMessage = "This item cannot be renamed from File Picker."
                Return False
            End If

            Dim sourcePath As String = ResolveExistingPhysicalPath(entry)
            If sourcePath.Length <= 0 Then
                errorMessage = "The selected item no longer exists."
                Return False
            End If

            Dim safeName As String = If(newName, String.Empty).Trim()
            If Not ValidateFileSystemName(safeName, errorMessage) Then
                Return False
            End If

            Dim parentPath As String = String.Empty

            Try
                parentPath = Path.GetDirectoryName(sourcePath)
            Catch masCaughtException17 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException17)
                errorMessage = "Could not resolve the parent folder."
                Return False
            End Try

            If String.IsNullOrWhiteSpace(parentPath) OrElse Not Directory.Exists(parentPath) Then
                errorMessage = "Could not resolve the parent folder."
                Return False
            End If

            Dim targetPath As String = Path.Combine(parentPath, safeName)

            If String.Equals(sourcePath, targetPath, StringComparison.OrdinalIgnoreCase) Then
                Return True
            End If

            If File.Exists(targetPath) OrElse Directory.Exists(targetPath) Then
                errorMessage = "An item with the same name already exists."
                Return False
            End If

            Try
                If File.Exists(sourcePath) Then
                    File.Move(sourcePath, targetPath)
                ElseIf Directory.Exists(sourcePath) Then
                    Directory.Move(sourcePath, targetPath)
                Else
                    errorMessage = "The selected item no longer exists."
                    Return False
                End If

                RefreshItems()
                RestoreSelectionByName(safeName)
                Return True

            Catch masCaughtException18 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException18)
                errorMessage = If(String.IsNullOrWhiteSpace(masCaughtException18.Message),
                                  "Rename failed.",
                                  masCaughtException18.Message)
                Return False
            End Try
        End Function

        Private Sub OpenEntry(entry As FileSystemEntry)
            If entry Is Nothing Then Return

            SelectBrowserEntry(entry)

            Dim path As String = ResolveEntryPath(entry)

            If entry.IsFolderLike AndAlso path.Length > 0 AndAlso Directory.Exists(path) Then
                NavigateTo(path)
                Return
            End If

            If _controller Is Nothing Then Return

            If CurrentOptions.Mode = MASFilePickerMode.SaveFile Then
                If entry.IsFile Then
                    FileName = entry.Name
                End If

                Return
            End If

            _controller.AcceptSelection()
        End Sub

        Private Shared Sub OpenEntryInWindowsShell(entry As FileSystemEntry)
            If entry Is Nothing Then Return

            Dim path As String = ResolveExistingPhysicalPath(entry)
            If path.Length <= 0 Then Return

            Try
                Dim args As String

                If File.Exists(path) Then
                    args = "/select," & QuoteShellPath(path)
                Else
                    args = QuoteShellPath(path)
                End If

                Process.Start(New ProcessStartInfo() With {
                    .FileName = "explorer.exe",
                    .Arguments = args,
                    .UseShellExecute = True
                })
            Catch masCaughtException19 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowNativeInterop(masCaughtException19)
            End Try
        End Sub

        Private Shared Sub ShowEntryProperties(entry As FileSystemEntry)
            If entry Is Nothing Then Return

            Dim path As String = ResolveExistingPhysicalPath(entry)
            If path.Length <= 0 Then Return

            If MASWindowsShellProperties.ShowProperties(path) Then
                Return
            End If

            Try
                Process.Start(New ProcessStartInfo(path) With {
                    .Verb = "properties",
                    .UseShellExecute = True
                })
            Catch masCaughtException20 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException20)
            End Try
        End Sub

        Private Shared Sub CopyEntryPathToClipboard(entry As FileSystemEntry)
            If entry Is Nothing Then Return

            Dim path As String = ResolveEntryPath(entry)
            If path.Length <= 0 Then Return

            Try
                Clipboard.SetText(path)
            Catch masCaughtException21 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowClipboardBoundary(masCaughtException21)
            End Try
        End Sub

        Private Shared Sub CopyEntryToClipboard(entry As FileSystemEntry,
                                                cut As Boolean)
            If entry Is Nothing Then Return

            Dim path As String = ResolveExistingPhysicalPath(entry)
            If path.Length <= 0 Then Return
            If cut AndAlso Not CanModifyPhysicalEntry(entry) Then Return

            Try
                Dim paths As New StringCollection()
                paths.Add(path)

                Dim data As New DataObject()
                data.SetFileDropList(paths)

                Dim effect As Integer = If(cut, 2, 1)
                Dim bytes As Byte() = BitConverter.GetBytes(effect)
                data.SetData("Preferred DropEffect", New MemoryStream(bytes))

                Clipboard.SetDataObject(data, True)
            Catch masCaughtException22 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowClipboardBoundary(masCaughtException22)
            End Try
        End Sub

        Private Sub DeleteEntryToRecycleBin(entry As FileSystemEntry)
            If entry Is Nothing Then Return
            If Not CanModifyPhysicalEntry(entry) Then Return

            Dim path As String = ResolveExistingPhysicalPath(entry)
            If path.Length <= 0 Then Return

            Try
                If File.Exists(path) Then
                    Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(
                        path,
                        Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
                        Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin)
                ElseIf Directory.Exists(path) Then
                    Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(
                        path,
                        Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
                        Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin)
                End If

                RefreshItems()
            Catch masCaughtException23 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException23)
            End Try
        End Sub

        Private Sub PasteClipboardIntoCurrentFolder()
            Dim targetFolder As String = ResolveCurrentFolderPhysicalPath()
            If targetFolder.Length <= 0 Then Return

            Dim paths As StringCollection = Nothing
            Dim cutOperation As Boolean = False

            Try
                If Not Clipboard.ContainsFileDropList() Then Return
                paths = Clipboard.GetFileDropList()
                cutOperation = IsClipboardCutOperation()
            Catch masCaughtException24 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowClipboardBoundary(masCaughtException24)
                Return
            End Try

            If paths Is Nothing OrElse paths.Count = 0 Then Return

            Dim completed As Integer = 0
            Dim failed As Boolean = False

            For Each source As String In paths
                Dim sourcePath As String = If(source, String.Empty).Trim()
                If sourcePath.Length <= 0 Then Continue For

                If CopyOrMoveFileSystemEntry(sourcePath, targetFolder, cutOperation) Then
                    completed += 1
                Else
                    failed = True
                End If
            Next

            If cutOperation AndAlso completed > 0 AndAlso Not failed Then
                Try
                    Clipboard.Clear()
                Catch masCaughtException25 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowClipboardBoundary(masCaughtException25)
                End Try
            End If

            If completed > 0 Then
                RefreshItems()
            End If
        End Sub

        Private Shared Function CopyOrMoveFileSystemEntry(sourcePath As String,
                                                          targetFolder As String,
                                                          cutOperation As Boolean) As Boolean
            If String.IsNullOrWhiteSpace(sourcePath) Then Return False
            If String.IsNullOrWhiteSpace(targetFolder) Then Return False
            If Not Directory.Exists(targetFolder) Then Return False

            Try
                Dim name As String = Path.GetFileName(sourcePath.TrimEnd("\"c))
                If String.IsNullOrWhiteSpace(name) Then Return False

                Dim targetPath As String = Path.Combine(targetFolder, name)

                If File.Exists(sourcePath) Then
                    If cutOperation Then
                        Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(
                            sourcePath,
                            targetPath,
                            Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
                            Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException)
                    Else
                        Microsoft.VisualBasic.FileIO.FileSystem.CopyFile(
                            sourcePath,
                            targetPath,
                            Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
                            Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException)
                    End If

                    Return True
                End If

                If Directory.Exists(sourcePath) Then
                    If cutOperation Then
                        Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(
                            sourcePath,
                            targetPath,
                            Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
                            Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException)
                    Else
                        Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(
                            sourcePath,
                            targetPath,
                            Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
                            Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException)
                    End If

                    Return True
                End If

            Catch masCaughtException26 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowClipboardBoundary(masCaughtException26)
            End Try

            Return False
        End Function

        Private Function ResolveCurrentFolderPhysicalPath() As String
            Dim path As String = If(CurrentPath, String.Empty).Trim()
            If path.Length <= 0 Then Return String.Empty

            Try
                If Directory.Exists(path) Then Return path
            Catch masCaughtException27 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException27)
            End Try

            Return String.Empty
        End Function

        Private Shared Function IsClipboardCutOperation() As Boolean
            Try
                Dim data As IDataObject = Clipboard.GetDataObject()
                If data Is Nothing Then Return False

                Dim value As Object = data.GetData("Preferred DropEffect")
                If value Is Nothing Then Return False

                Dim effect As Integer = 0

                If TypeOf value Is MemoryStream Then
                    Using ms As MemoryStream = DirectCast(value, MemoryStream)
                        Dim bytes As Byte() = ms.ToArray()
                        If bytes.Length >= 4 Then effect = BitConverter.ToInt32(bytes, 0)
                    End Using
                ElseIf TypeOf value Is Byte() Then
                    Dim bytes As Byte() = DirectCast(value, Byte())
                    If bytes.Length >= 4 Then effect = BitConverter.ToInt32(bytes, 0)
                ElseIf TypeOf value Is Integer Then
                    effect = DirectCast(value, Integer)
                End If

                Return effect = 2
            Catch masCaughtException28 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowClipboardBoundary(masCaughtException28)
                Return False
            End Try
        End Function

        Private Shared Function ValidateFileSystemName(name As String,
                                                       ByRef errorMessage As String) As Boolean
            errorMessage = String.Empty

            Dim n As String = If(name, String.Empty).Trim()
            If n.Length = 0 Then
                errorMessage = "Name cannot be empty."
                Return False
            End If

            If n = "." OrElse n = ".." Then
                errorMessage = "This name is reserved."
                Return False
            End If

            If n.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 Then
                errorMessage = "The name contains characters that are not allowed in file names."
                Return False
            End If

            Return True
        End Function

        Private Shared Function CanModifyPhysicalEntry(entry As FileSystemEntry) As Boolean
            If entry Is Nothing Then Return False

            Select Case entry.EntryType
                Case FileSystemEntryType.FileItem,
                     FileSystemEntryType.Folder
                    ' Continue below.

                Case Else
                    Return False
            End Select

            Dim fullPath As String = ResolveExistingPhysicalPath(entry)
            If fullPath.Length <= 0 Then Return False

            Try
                Dim root As String = System.IO.Path.GetPathRoot(fullPath)
                Dim normalizedPath As String = fullPath.TrimEnd("\"c)
                Dim normalizedRoot As String = If(root, String.Empty).TrimEnd("\"c)

                If normalizedRoot.Length > 0 AndAlso
                   String.Equals(normalizedPath, normalizedRoot, StringComparison.OrdinalIgnoreCase) Then
                    Return False
                End If
            Catch masCaughtException29 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException29)
                Return False
            End Try

            Return True
        End Function

        Private Shared Function ResolveExistingPhysicalPath(entry As FileSystemEntry) As String
            Dim path As String = ResolveEntryPath(entry)
            If path.Length <= 0 Then Return String.Empty

            Try
                If File.Exists(path) OrElse Directory.Exists(path) Then
                    Return path
                End If
            Catch masCaughtException30 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowFileSystemBoundary(masCaughtException30)
            End Try

            Return String.Empty
        End Function

        Private Shared Function ResolveEntryPath(entry As FileSystemEntry) As String
            If entry Is Nothing Then Return String.Empty
            Return If(entry.FullPath, String.Empty).Trim()
        End Function

        Private Shared Function QuoteShellPath(path As String) As String
            Dim safe As String = If(path, String.Empty)
            Return """" & safe & """"
        End Function

        Private Sub SelectBrowserEntry(entry As FileSystemEntry)
            If entry Is Nothing OrElse _browser Is Nothing Then Return

            For i As Integer = 0 To _browser.Items.Count - 1
                If Object.ReferenceEquals(_browser.Items(i), entry) Then
                    _browser.SelectIndex(i)
                    _browser.EnsureVisible(i)
                    Return
                End If
            Next
        End Sub

#End Region


    End Class

End Namespace
