﻿''' <summary>
''' Represents a managed wrapper around a Windows file.
''' </summary>
Public Class ShellFile
    Implements IDisposable

    '//fields
    Private disposedValue As Boolean

    '//properties
    ''' <summary>
    ''' Gets the extension of the file.
    ''' </summary>
    Public ReadOnly Property Extension() As String
        Get
            Return IO.Path.GetExtension(Me.Path)
        End Get
    End Property

    ''' <summary>
    ''' Gets the directory path of the file.
    ''' </summary>
    Public ReadOnly Property Directory() As String
        Get
            Return IO.Path.GetDirectoryName(Me.Path)
        End Get
    End Property

    ''' <summary>
    ''' Gets the file name with extension.
    ''' </summary>
    Public ReadOnly Property FileName() As String
        Get
            Return IO.Path.GetFileName(Me.Path)
        End Get
    End Property

    Private _path As String
    ''' <summary>
    ''' Gets the full path of the file.
    ''' </summary>
    Public ReadOnly Property Path() As String
        Get
            Return Me._path
        End Get
    End Property

    Private _shell As Shell32.Shell
    ''' <summary>
    ''' Gets the sole instance that this class will maintain for retrieving file system information.
    ''' </summary>
    Protected ReadOnly Property Shell() As Shell32.Shell
        Get
            If Me._shell Is Nothing Then
                Me._shell = New Shell32.Shell()
            End If
            Return Me._shell
        End Get
    End Property

    Private _extendedProperties As ExtendedPropertyCollection
    ''' <summary>
    ''' Gets a collection of extended properties that describe this file.
    ''' </summary>
    Public ReadOnly Property ExtendedProperties() As ExtendedPropertyCollection
        Get
            Return Me._extendedProperties
        End Get
    End Property

    '//constructors
    ''' <summary>
    ''' Initializes a new instance of the ShellFile class using the specified file path.
    ''' </summary>
    ''' <param name="path">Indicates the path of the file for this class to represent.</param>
    Public Sub New(ByVal path As String)
        If String.IsNullOrEmpty(path) Then
            Throw New ArgumentNullException("path")
        End If
        Me._path = path
        Me.Populate()
    End Sub

    '//methods
    Private Sub Populate()
        Dim collection = Me.GetCollection()
        If collection IsNot Nothing Then
            Me._extendedProperties = New ExtendedPropertyCollection(collection)
        End If
    End Sub

    Private Function GetCollection() As ICollection
        Dim properties = New List(Of ExtendedProperty)()
        Dim shell = Me.Shell
        Dim directory = Me.Directory
        If Not String.IsNullOrEmpty(directory) Then
            Dim fileName = Me.FileName
            Dim folder = shell.NameSpace(directory)
            For Each item As Shell32.FolderItem2 In folder.Items()
                If item.Name.Equals(fileName) Then
                    Dim index = 0
                    Dim name = String.Empty
                    Do
                        name = folder.GetDetailsOf(folder, index)
                        If String.IsNullOrEmpty(name) Then
                            Return properties
                        End If
                        properties.Add(New ExtendedProperty(name, folder.GetDetailsOf(item, index)))
                        index += 1
                    Loop
                End If
            Next
        End If
        Return Nothing
    End Function

    ''' <summary>
    ''' Releases all unmanaged resources used by the instance; and optionally disposes all managed resources.
    ''' </summary>
    ''' <param name="disposing">True to dispose both unmanaged and managed resources; false to release only unmanaged.</param>
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                If Me._shell IsNot Nothing Then
                    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(Me._shell)
                End If
                Me._shell = Nothing
            End If
        End If
        Me.disposedValue = True
    End Sub

    ''' <summary>
    ''' Releases all unmanaged resources used by the instance; and optionally disposes all managed resources.
    ''' </summary>
    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        System.GC.SuppressFinalize(Me)
    End Sub

    ''' <summary>
    ''' Determines whether the file exists.
    ''' </summary>
    Public Function Exists() As Boolean
        Return IO.File.Exists(Me.Path)
    End Function

End Class

''' <summary>
''' Represents an instance of a Windows extended property. This class cannot be inherited.
''' </summary>
Public NotInheritable Class ExtendedProperty

    '//properties
    Private _name As String
    ''' <summary>
    ''' Gets the name of the property.
    ''' </summary>
    Public ReadOnly Property Name() As String
        Get
            Return Me._name
        End Get
    End Property

    Private _value As String
    ''' <summary>
    ''' Gets the value of the property.
    ''' </summary>
    Public ReadOnly Property Value() As String
        Get
            Return Me._value
        End Get
    End Property

    '//constructors
    ''' <summary>
    ''' Initializes a new instance of the ExtendedProperty class.
    ''' </summary>
    ''' <param name="name">The name of the property.</param>
    ''' <param name="value">The value of the property.</param>
    Public Sub New(ByVal name As String, ByVal value As String)
        Me._name = name
        Me._value = value
    End Sub

End Class

''' <summary>
''' Represents a read-only collection of ExtendedProperty objects. This class cannot be inherited.
''' </summary>
Public NotInheritable Class ExtendedPropertyCollection
    Inherits ReadOnlyCollectionBase

    '//properties
    ''' <summary>
    ''' Gets the element at the specified index.
    ''' </summary>
    ''' <param name="index">Indicates the index of the element to retrieve from the collection.</param>
    Default Public ReadOnly Property Item(ByVal index As Integer) As ExtendedProperty
        Get
            Return DirectCast(MyBase.InnerList(index), ExtendedProperty)
        End Get
    End Property

    ''' <summary>
    ''' Gets the first element with the specified name.
    ''' </summary>
    ''' <param name="name">Indicates the name of the element to retrieve from the collection.</param>
    Default Public ReadOnly Property Item(ByVal name As String) As ExtendedProperty
        Get
            Return Me.GetTyped().FirstOrDefault(Function(ep) _
                                                    String.Compare(ep.Name, name, _
                                                                   True).Equals(0))
        End Get
    End Property

    '//constructors
    ''' <summary>
    ''' Initializes a new instance of the ExtendedPropertyCollection class.
    ''' </summary>
    ''' <param name="collection">Represents the collection that will be contained by this instance.</param>
    Public Sub New(ByVal collection As ICollection)
        MyBase.New()
        MyBase.InnerList.AddRange(collection)
    End Sub

    '//methods
    Private Function GetTyped() As IEnumerable(Of ExtendedProperty)
        Return MyBase.Cast(Of ExtendedProperty)()
    End Function

End Class