'
'ListViewSortComparer was made by William Wood 2005.
'

''' <summary>
''' Allows you to sort all the items in a <see cref="ListView">ListView</see> control by different columns, orders and datatypes.
''' </summary>
''' <remarks>
''' <example>
''' This example shows you how to sort a collection of items in the second column of a <see cref="ListView">ListView</see> control 
''' containing numbers in descending order.
''' <code>
''' ListView1.ListViewItemSorter = New ListViewSortComparer(1, SortOrder.Descending, ListViewSortComparer.SourceDataType.Numerical)
''' ListView1.Sort()
''' </code>
''' </example>
''' </remarks>
Public Class ListViewSortComparer
    Implements System.Collections.IComparer

    ''' <summary>
    ''' Indicates the column index to be sorted.
    ''' </summary>
    Private _ColumnIndex As Integer
    ''' <summary>
    ''' Holds the order in which the data is to be sorted.
    ''' </summary>
    Private _SortOrder As SortOrder = SortOrder.Ascending
    ''' <summary>
    ''' Holds the type of data in the column so the appropiate comparer can be used
    ''' </summary>
    Private _DataType As SourceDataType = SourceDataType.Text

#Region " Overloaded Public Sub New "
    ''' <summary>
    ''' Creates a new instance of the comparer, the order used to sort the <see cref="ListViewItem">ListViewItems</see> in the first column.
    ''' </summary>
    ''' <param name="sort">The <see cref="SortOrder">SortOrder</see> type specifying the order in which the data in <paramref name="columnIndex">columnIndex</paramref> 
    ''' should be sorted.</param>
    ''' <remarks>
    ''' Performs a text based comparison of the items in the first column of the <see cref="ListView">ListView</see> control that uses this comparer using the sort order specified 
    ''' by <paramref name="sort">sort</paramref>.
    ''' </remarks>
    Public Sub New(ByVal sort As SortOrder)
        _ColumnIndex = 0
        _SortOrder = sort
    End Sub

    ''' <summary>
    ''' Creates a new instance of the comparer, specifying the column to sort by and the order used to sort the <see cref="ListViewItem">ListViewItems</see>.
    ''' </summary>
    ''' <param name="columnIndex">The zero-based index specifying the column to sort.</param>
    ''' <param name="sort">The <see cref="SortOrder">SortOrder</see> type specifying the order in which the data in <paramref name="columnIndex">columnIndex</paramref> 
    ''' should be sorted.</param>
    ''' <remarks>
    ''' Performs a text based comparison of the items in the <see cref="ListView">ListView</see> control that uses this comparer using the options specified 
    ''' by the arguments above.
    ''' </remarks>
    Public Sub New(ByVal columnIndex As Integer, ByVal sort As SortOrder)
        _ColumnIndex = columnIndex
        _SortOrder = sort
    End Sub

    ''' <summary>
    ''' Creates a new instance of the comparer, specifying the column to sort by and the order used to sort the <see cref="ListViewItem">ListViewItems</see> and the 
    ''' datatype of the elements in the column.
    ''' </summary>
    ''' <param name="columnIndex">The zero-based index specifying the column to sort.</param>
    ''' <param name="sort">The <see cref="SortOrder">SortOrder</see> type specifying the order in which the data in <paramref name="columnIndex">columnIndex</paramref> 
    ''' should be sorted.</param>
    ''' <param name="dataType">The datatype of the items in the column specified by <paramref name="columnIndex">columnIndex</paramref>.</param>
    ''' <remarks>
    ''' <para>Performs a comparision of all the items in the <see cref="ListView">ListView</see> control using the datatype specified by the <paramref name="dataType">dataType</paramref> 
    ''' argument. You must ensure you select the correct datatype for all the items, otherwise you may receive unexpected results.</para>
    ''' <para>For Example, if you sort a collection of numbers as text, it will sort them as 1,10,11,2,22,25,7,8 instead of 1,2,7,8,10,11,22,25.</para>
    ''' </remarks>
    Public Sub New(ByVal columnIndex As Integer, ByVal sort As SortOrder, ByVal dataType As SourceDataType)
        _ColumnIndex = columnIndex
        _SortOrder = sort
        _DataType = dataType
    End Sub
#End Region

    ''' <summary>
    ''' Compares two <see cref="ListViewItem">ListViewItems</see> using a datatype comparison specified by <see cref="SourceDataType">SourceDataType</see>.
    ''' </summary>
    ''' <param name="object1">The first item to compare.</param>
    ''' <param name="object2">The second item to compare.</param>
    ''' <returns>A value indicating the positions of the two passed objects relative to each other.
    ''' <list type="table">
    ''' <listheader>
    ''' <term>Return Value</term>
    ''' <description>Meaning</description>
    ''' </listheader>
    ''' <item><term>Less than zero</term><description><paramref name="object1">object1</paramref> is less than <paramref name="object2">object2</paramref>.</description></item>
    ''' <item><term>Zero</term><description><paramref name="object1">object1</paramref> is equal to <paramref name="object2">object2</paramref>.</description></item>
    ''' <item><term>Greater than zero</term><description><paramref name="object1">object1</paramref> is greater than <paramref name="object2">object2</paramref>.</description></item>
    ''' </list>
    ''' </returns>
    ''' <remarks>
    ''' <para>Compares all the items in the <see cref="ListView">ListView</see> control to allow them to be sorted.</para>
    ''' <para>This function uses the parameters set in the class's constructor to specify its properties such as the sort order and the datatype comparison to be used.</para>
    ''' <para>This function will also automatically move null items to the top of the collection when performing an ascending sort, or move them to the bottom of the collection 
    ''' when performing a descending sort.</para>
    ''' </remarks>
    ''' <exception cref="System.FormatException"><para>This exception is thrown when the DataType is invalid for the type of data present in the column.</para>
    ''' <para>For example, sorting a collection of names as <see cref="SourceDataType.DateTime">SourceDataType.DateTime</see>.</para></exception>
    Public Function Compare(ByVal object1 As Object, ByVal object2 As Object) As Integer Implements System.Collections.IComparer.Compare
        '
        'Holds the two items to be compared, converting them both to ListViewItems
        Dim ListViewItem1 As ListViewItem = CType(object1, ListViewItem)
        Dim ListViewItem2 As ListViewItem = CType(object2, ListViewItem)

        '
        'Initial null checking to ensure that there is valid data in the item being compared
        'to reduce the chance of exceptions being raised due to no objects being referenced
        If CheckNulls(ListViewItem1, ListViewItem2) <> 0 Then 'Checks to see if CheckNulls doesn't return 0 i.e. there is a null SubItem present
            Return CheckNulls(ListViewItem1, ListViewItem2) 'It returned 0. Returns the position returned by CheckNulls that indicates where the null item should be moved to
        End If

        If _SortOrder = SortOrder.Ascending Then  'Checks to see if it should sort in Ascending order
            'It should sort in ascending order

            If _DataType = SourceDataType.Text Then 'Checks to see if it should sort the items as text
                'It should sort the items as text
                Return String.Compare(ListViewItem1.SubItems(_ColumnIndex).Text, ListViewItem2.SubItems(_ColumnIndex).Text)

            ElseIf _DataType = SourceDataType.Numerical Then 'Checks to see if it should sort the items as integers
                'It should sort the items as Integers
                Return Integer.Parse(ListViewItem1.SubItems(_ColumnIndex).Text) - Integer.Parse(ListViewItem2.SubItems(_ColumnIndex).Text)

            ElseIf _DataType = SourceDataType.DateTime Then 'Checks to see if it should sort the items as date/time
                'It should sort the items as date/time
                Return Date.Compare(Date.Parse(ListViewItem1.SubItems(_ColumnIndex).Text), Date.Parse(ListViewItem2.SubItems(_ColumnIndex).Text))

            ElseIf _DataType = SourceDataType.Percentage Then 'Checks to see if it should sort the items as percentages
                'It should sort the items as percentages
                'Return Single.Parse(CleanPercentage(ListViewItem1.SubItems(_ColumnIndex).Text)) - Single.Parse(CleanPercentage(ListViewItem2.SubItems(_ColumnIndex).Text))
                Return SingleComparer(Single.Parse(CleanPercentage(ListViewItem1.SubItems(_ColumnIndex).Text)), Single.Parse(CleanPercentage(ListViewItem2.SubItems(_ColumnIndex).Text)))

            ElseIf _DataType = SourceDataType.Single Then 'Checks to see if it should sort the items as singles
                'It should sort the items as singles
                'Return Single.Parse(ListViewItem1.SubItems(_ColumnIndex).Text) - Single.Parse(ListViewItem2.SubItems(_ColumnIndex).Text)
                Return SingleComparer(Single.Parse(ListViewItem1.SubItems(_ColumnIndex).Text), Single.Parse(ListViewItem2.SubItems(_ColumnIndex).Text))
            End If

        ElseIf _SortOrder = SortOrder.Descending Then 'Checks to see if it should sort in Descending order
            'It should sort in descending order

            If _DataType = SourceDataType.Text Then 'Checks to see if it should sort the items as text
                'It should sort the items as text
                Return String.Compare(ListViewItem2.SubItems(_ColumnIndex).Text, ListViewItem1.SubItems(_ColumnIndex).Text)

            ElseIf _DataType = SourceDataType.Numerical Then 'Checks to see if it should sort the items as integers
                'It should sort the items as Integers
                Return Integer.Parse(ListViewItem2.SubItems(_ColumnIndex).Text) - Integer.Parse(ListViewItem1.SubItems(_ColumnIndex).Text)

            ElseIf _DataType = SourceDataType.DateTime Then 'Checks to see if it should sort the items as date/time
                'It should sort the items as date/time
                Return Date.Compare(Date.Parse(ListViewItem2.SubItems(_ColumnIndex).Text), Date.Parse(ListViewItem1.SubItems(_ColumnIndex).Text))

            ElseIf _DataType = SourceDataType.Percentage Then 'Checks to see if it should sort the items as percentages
                'It should sort the items as percentages
                Return SingleComparer(Single.Parse(CleanPercentage(ListViewItem2.SubItems(_ColumnIndex).Text)), Single.Parse(CleanPercentage(ListViewItem1.SubItems(_ColumnIndex).Text)))

            ElseIf _DataType = SourceDataType.Single Then 'Checks to see if it should sort the items as singles
                'It should sort the items as singles
                Return SingleComparer(Single.Parse(ListViewItem2.SubItems(_ColumnIndex).Text), Single.Parse(ListViewItem1.SubItems(_ColumnIndex).Text))
            End If
        End If

        Return 0 'Returns 0 because no valid comparison could be made
    End Function

    ''' <summary>
    ''' Checks the passed <see cref="ListViewItem">ListViewItems</see> to see if they contain valid data for comparisons.
    ''' </summary>
    ''' <param name="listViewItem1">The first <see cref="ListViewItem">ListViewItem</see> that will be used in a comparison.</param>
    ''' <param name="listViewItem2">The second <see cref="ListViewItem">ListViewItem</see> that will be used in a comparison.</param>
    ''' <returns>A value indicating whether it is valid or not.</returns>
    ''' <remarks><para>This function checks the values of <paramref name="listViewItem1">listViewItem1</paramref> and 
    ''' <paramref name="listViewItem2">listViewItem2</paramref> to ensure that they contain valid data for comparisons to be made.</para>
    ''' <para>It does this by first checking that the column index passed to the comparer to compare on is a valid item in each of the ListViewItems 
    ''' (i.e. it exists in their <see cref="ListViewItem.SubItems">SubItems</see> collections). It will then check to see if it actually contains any data 
    ''' for a comparison to be made.</para>
    ''' <para>If the two checks checkout for both of the arguments, then the function will return a value of 0 which indicates that the arguments contain 
    ''' valid data for a comparison to be made.</para>
    ''' <para>However, if either of the two checks fail for either of the arguments, then the function will return a valid of either -1 or 1 which 
    ''' indicates the position of <paramref name="listViewItem1">listViewItem1</paramref> in comparision to <paramref name="listViewItem2">listViewItem2</paramref>
    ''' which can then be used by the calling subroutine to use as it's return value if it's a comparer.</para>
    ''' <para>Please note, this function does not compare the value of items, this needs to be done after the checking performed by this function. This 
    ''' function mearly checks for invalid data that may cause a comparer to raise an exception if a null item is encounted so they can be automatically 
    ''' moved to the top or bottom depending on the sort performed.</para></remarks>
    Private Function CheckNulls(ByVal listViewItem1 As ListViewItem, ByVal listViewItem2 As ListViewItem) As Integer
        If _SortOrder = SortOrder.Ascending Then 'Checks to see if it should perform an ascending sort
            '
            'It should sort in ascending order
            If listViewItem1.SubItems.Count <= _ColumnIndex Then 'Checks to see if the number of items in ListViewItem1 is <= the column index supplied i.e. no subitem exists at that position
                Return -1 'The number of SubItems is <= _ColumnIndex. Returns -1 indicating that it should be moved up
            End If

            If listViewItem2.SubItems.Count <= _ColumnIndex Then 'Checks to see if the number of items in ListViewItem2 is <= the column index supplied i.e. no subitem exists at that position
                Return 1 'The number of SubItems is <= _ColumnIndex. Returns 1 indicating that it should be moved down
            End If

            '
            'Null item present in a SubItem check
            If listViewItem1.SubItems(_ColumnIndex).Text = "" Then 'Checks to see if the subitem at _ColumnIndex in ListViewItem1 is a null
                Return -1 'The subitem is a null. Returns -1 indicating that it should be moved up
            End If

            If listViewItem2.SubItems(_ColumnIndex).Text = "" Then 'Checks to see if the subitem at _ColumnIndex in ListViewItem2 is a null
                Return 1 'The subitem is a null. Returns 1 indicating that it should be moved down
            End If

        ElseIf _SortOrder = SortOrder.Descending Then 'Checks to see if it should perform a descending sort
            '
            'It should sort in descending order
            If listViewItem1.SubItems.Count <= _ColumnIndex Then 'Checks to see if the number of items in ListViewItem1 is <= the column index supplied i.e. no subitem exists at that position
                Return 1 'The number of SubItems is <= _ColumnIndex. Returns 1 indicating that it should be moved down
            End If

            If listViewItem2.SubItems.Count <= _ColumnIndex Then 'Checks to see if the number of items in ListViewItem2 is <= the column index supplied i.e. no subitem exists at that position
                Return -1 'The number of SubItems is <= _ColumnIndex. Returns -1 indicating that it should be moved up
            End If

            '
            'Null item present in a SubItem check
            If listViewItem1.SubItems(_ColumnIndex).Text = "" Then 'Checks to see if the subitem at _ColumnIndex in ListViewItem1 is a null
                Return 1 'The subitem is a null. Returns 1 indicating that it should be moved down
            End If

            If listViewItem2.SubItems(_ColumnIndex).Text = "" Then 'Checks to see if the subitem at _ColumnIndex in ListViewItem2 is a null
                Return -1 'The subitem is a null. Returns -1 indicating that it should be moved up
            End If
        End If

        Return 0 'Returns 0 because no nulls were found
    End Function

    ''' <summary>
    ''' Removes any percentage signs (%) and spaces from the <paramref name="sourceData">sourceData</paramref>
    ''' argument and returns the clean form as a string.
    ''' </summary>
    ''' <param name="sourceData">The string containing the percentage in its raw form.</param>
    ''' <returns>The percentage value containing just the <see cref="system.Single">single</see>
    ''' data.</returns>
    ''' <remarks><para>The function assumes that the argument <paramref name="sourceData">sourceData</paramref> 
    ''' is in the form 0.##% (See article on string formatting for more information [todo make link]) where any number 
    ''' of spaces may be present in the string.</para>
    ''' <para>Using this information, it will strip any spaces and percentage signs (%) from the string and return the 
    ''' result as a <see cref="system.String">string</see>.</para></remarks>
    Private Function CleanPercentage(ByVal sourceData As String) As String
        Return sourceData.Trim(" ", "%") 'Removes any percentage and space characters from the string
    End Function

    ''' <summary>
    ''' Compares two <see cref="System.Single">single</see> values and returns their comparison.
    ''' </summary>
    ''' <param name="single1">The first <see cref="system.Single">single</see> to compare.</param>
    ''' <param name="single2">The second <see cref="system.Single">single</see> to compare.</param>
    ''' <returns>A value indicating the positions of the two <see cref="system.Single">singles</see> relative to each other.
    ''' <list type="table">
    ''' <listheader>
    ''' <term>Return Value</term>
    ''' <description>Meaning</description>
    ''' </listheader>
    ''' <item><term>Less than zero</term><description><paramref name="single1">single1</paramref> is less than <paramref name="single2">single2</paramref>.</description></item>
    ''' <item><term>Zero</term><description><paramref name="single1">single1</paramref> is equal to <paramref name="single2">single2</paramref>.</description></item>
    ''' <item><term>Greater than zero</term><description><paramref name="single1">single1</paramref> is greater than <paramref name="single2">single2</paramref>.</description></item>
    ''' </list>
    ''' </returns>
    Private Function SingleComparer(ByVal single1 As Single, ByVal single2 As Single) As Integer
        Dim Result As Single = (single1 - single2) 'Subtracts single2 from single1 and stores the result in Result

        '
        'Loops while the absolute number of Result < 1.
        'Loops because when a single is converted to an integer, if it is < 1 then the decimal
        'part is discarded and appears to be the same (i.e. 0 indicates they're the same in a comparison).
        Do While Math.Abs(Result) < 1
            Result *= 10 'Multiplies Result by 10 to move the decimal point one place to the right
        Loop

        Return Result 'Returns the result in a form that can be interperated by Integers
    End Function

    ''' <summary>
    ''' Indicates the type of data present when used with the <see cref="ListViewSortComparer">ListViewSortComparer</see> class.
    ''' </summary>
    Public Enum SourceDataType
        ''' <summary>
        ''' Indicates that the type of the data is text (<see cref="System.String">String</see>).
        ''' </summary>
        Text
        ''' <summary>
        ''' Indicates that the type of the data is <see cref="Int32">integers</see>.
        ''' </summary>
        Numerical
        ''' <summary>
        ''' Indicates that the type of the data is <see cref="System.Date">dates and/or times</see>.
        ''' </summary>
        DateTime
        ''' <summary>
        ''' Indicates that the type of the data is <see cref="System.Single">singles</see>.
        ''' </summary>
        [Single]
        ''' <summary>
        ''' Indicates that the type of the data is percentages.
        ''' </summary>
        Percentage
    End Enum

End Class
