'  
'   Rebex Sample Code License
' 
'   Copyright 2025, REBEX CR s.r.o.
'   All rights reserved.
'   https://www.rebex.net/
' 
'   Permission to use, copy, modify, and/or distribute this software for any
'   purpose with or without fee is hereby granted.
' 
'   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
'   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
'   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
'   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
'   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
'   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
'   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
'   OTHER DEALINGS IN THE SOFTWARE.
' 

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Windows.Forms
Imports System.IO

Imports Rebex.IO
Imports Rebex.IO.Compression

''' <summary>
''' Provides various helper methods.
''' </summary>
Public NotInheritable Class Utils
    ''' <summary>
    ''' Path of the ZIP archive root.
    ''' </summary>
    Public Shared ReadOnly RootPath = Path.DirectorySeparatorChar.ToString()

    ''' <summary>
    ''' Rebuilds the specified TreeView according to the specified ZIP archive content, persisting currently selected and expanded nodes.
    ''' </summary>
    ''' <param name="treeView">TreeView control to rebuild.</param>
    ''' <param name="zipArchive">A ZIP archive instance.</param>
    Public Shared Sub FillTreeView(ByVal treeView As TreeView, ByVal zipArchive As ZipArchive)
        ' save selected node
        Dim selectedNodeItem As ZipItem = Nothing
        If treeView.SelectedNode IsNot Nothing Then
            selectedNodeItem = CType(treeView.SelectedNode.Tag, ZipItem)
        End If

        ' save expanded nodes into a dictionary
        Dim expandedNodeItems As New Dictionary(Of ZipItem, Object)()
        For Each subNode As TreeNode In treeView.Nodes
            ' call recursive method to get all expanded nodes
            GetExpandedNodes(subNode, expandedNodeItems)
        Next

        ' clear the TreeView before rebuilding
        treeView.Nodes.Clear()

        ' create the root node with the name of the archive file
        Dim rootNode As New TreeNode(String.Format("({0})", Path.GetFileName(zipArchive.FilePath)))
        rootNode.Tag = zipArchive.GetItem(RootPath)

        ' recursively add all subdirectories and get the node to select
        Dim selectedNode As TreeNode = AddSubdirectories(rootNode, zipArchive, expandedNodeItems, selectedNodeItem)

        ' add root node to the TreeView
        treeView.Nodes.Add(rootNode)

        ' set selected node
        treeView.SelectedNode = selectedNode
    End Sub

    ''' <summary>
    ''' Saves all expanded nodes into a dictionary.
    ''' </summary>
    ''' <param name="node">Currently processed directory node.</param>
    ''' <param name="expandedNodes">Dictionary of expanded nodes to fill.</param>
    Private Shared Sub GetExpandedNodes(ByVal node As TreeNode, ByVal expandedNodes As Dictionary(Of ZipItem, Object))
        ' if the node is expanded, add it to the dictionary
        If node.IsExpanded Then
            expandedNodes.Add(CType(node.Tag, ZipItem), Nothing)
        End If

        ' do the same for all expanded subnodes
        For Each subNode As TreeNode In node.Nodes
            GetExpandedNodes(subNode, expandedNodes)
        Next
    End Sub

    ''' <summary>
    ''' Recursively adds subdirectories to the specified directory node, expanding them if necessary.
    ''' </summary>
    ''' <param name="node">Directory node to add subdirectories to.</param>
    ''' <param name="zipArchive">A ZIP archive instance.</param>
    ''' <param name="expandedNodeItems">Dictionary of expanded nodes.</param>
    ''' <param name="selectedNodeItem">Selected item.</param>
    Private Shared Function AddSubdirectories(ByVal node As TreeNode, ByVal zipArchive As ZipArchive, _
        ByVal expandedNodeItems As Dictionary(Of ZipItem, Object), ByVal selectedNodeItem As ZipItem) As TreeNode

        Dim selectedNode As TreeNode = Nothing

        ' get subdirectories of the current directory node
        Dim item As ZipItem = CType(node.Tag, ZipItem)
        Dim directories As ZipItemCollection = zipArchive.GetItems(Path.Combine(item.Path, "*"), TraversalMode.NonRecursive, ArchiveItemTypes.Directories)
        directories.Sort()

        ' add subdirectories first!
        For Each directory As ZipItem In directories
            ' add new tree node
            Dim treeNode As New TreeNode(directory.Name)
            treeNode.Tag = directory
            node.Nodes.Add(treeNode)

            ' recursively add subdirectories
            Dim selectedSubNode As TreeNode = AddSubdirectories(treeNode, zipArchive, expandedNodeItems, selectedNodeItem)
            If selectedSubNode IsNot Nothing Then
                selectedNode = selectedSubNode
            End If
        Next

        ' check whether to expand
        If expandedNodeItems.ContainsKey(item) Then
            node.Expand()
        End If

        ' check whether to select
        If item.Equals(selectedNodeItem) Then
            Return node
        Else
            Return selectedNode
        End If
    End Function

    ''' <summary>
    ''' Fill the specified ListView with files of the selected directory node.
    ''' </summary>
    ''' <param name="ZipArchive">An archive instance.</param>
    ''' <param name="listView">ListView control to fill.</param>
    ''' <param name="selectedNode">Currently selected directory node.</param>
    Public Shared Sub FillListView(ByVal zipArchive As ZipArchive, ByVal listView As ListView, ByVal selectedNode As TreeNode)
        ' clear selection to raise registered events
        listView.SelectedItems.Clear()

        listView.Items.Clear()

        If selectedNode Is Nothing Then
            Return
        End If

        ' get files of the selected directory node
        Dim selectedItem As ZipItem = CType(selectedNode.Tag, ZipItem)
        Dim items As ZipItemCollection = zipArchive.GetItems(Path.Combine(selectedItem.Path, "*"), TraversalMode.NonRecursive, ArchiveItemTypes.Files)

        Dim listViewItems As New List(Of ListViewItem)()
        For Each item As ZipItem In items
            ' add a new ListViewItem for every file
            Dim listViewItem As New ListViewItem(item.Name)

            AddSubItem(listViewItem, item.Length)
            AddSubItem(listViewItem, item.CompressedLength)

            If item.Length > 0 Then
                ' compute compression ratio 
                Dim ratio = CInt((100 * item.CompressedLength / item.Length))
                AddSubItem(listViewItem, ratio, String.Format("{0}%", ratio))
            Else
                AddSubItem(listViewItem, Integer.MaxValue, "N/A")
            End If

            AddSubItem(listViewItem, item.LastWriteTime)

            listViewItem.Tag = item

            If item.IsEncrypted Then
                listViewItem.ImageIndex = 0
            End If

            listViewItems.Add(listViewItem)
        Next

        listView.Items.AddRange(listViewItems.ToArray())
    End Sub

    ''' <summary>
    ''' Adds a value to the specified ListViewItem.
    ''' </summary>
    ''' <param name="item">ListViewItem to which to add the value.</param>
    ''' <param name="value">A value to add.</param>
    Private Shared Sub AddSubItem(ByVal item As ListViewItem, ByVal value As Object)
        AddSubItem(item, value, value.ToString())
    End Sub

    ''' <summary>
    ''' Adds a value to the specified ListViewItem.
    ''' </summary>
    ''' <param name="item">ListViewItem to which to add the value.</param>
    ''' <param name="tag">A value to add.</param>
    ''' <param name="text">Description.</param>
    Private Shared Sub AddSubItem(ByVal item As ListViewItem, ByVal tag As Object, ByVal text As String)
        Dim subItem As New ListViewItem.ListViewSubItem()
        subItem.Text = text
        subItem.Tag = tag
        item.SubItems.Add(subItem)
    End Sub
End Class
