using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using SabreTools.Data.Extensions;
using SabreTools.Data.Models.COFF;
using SabreTools.Data.Models.COFF.SymbolTableEntries;
using SabreTools.Data.Models.PortableExecutable;
using SabreTools.Data.Models.PortableExecutable.Resource.Entries;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;

namespace SabreTools.Wrappers
{
    public partial class PortableExecutable : IPrintable
    {
#if NETCOREAPP
        /// <inheritdoc/>
        public string ExportJSON(bool recursive) => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
#else
        /// <inheritdoc/>
        public string ExportJSON(bool recursive) => Newtonsoft.Json.JsonConvert.SerializeObject(Model, _jsonSerializerOptions);
#endif

        /// <inheritdoc/>
        public void PrintInformation(StringBuilder builder, bool recursive)
        {
            builder.AppendLine("Portable Executable Information:");
            builder.AppendLine("-------------------------");
            builder.AppendLine();

            // Stub
            Print(builder, Model.Stub.Header);

            // Header
            Print(builder, Model.Signature, Model.FileHeader);
            Print(builder, Model.OptionalHeader, Model.SectionTable);

            // COFF Tables
            Print(builder, Model.SectionTable);
            Print(builder, Model.SymbolTable);
            Print(builder, Model.StringTable);

            // Export Table
            Print(builder, Model.ExportDirectoryTable, Model.SectionTable);
            Print(builder, Model.ExportAddressTable, Model.SectionTable);
            Print(builder, Model.NamePointerTable);
            Print(builder, Model.OrdinalTable);
            Print(builder, Model.ExportNameTable);

            // Import Table
            Print(builder, Model.ImportDirectoryTable, Model.SectionTable);
            Print(builder, Model.ImportLookupTables, Model.SectionTable);
            Print(builder, Model.ImportAddressTables, Model.SectionTable);
            Print(builder, Model.HintNameTable);

            // Resource Table
            Print(builder, Model.ResourceDirectoryTable, Model.SectionTable);

            Print(builder, Model.AttributeCertificateTable);
            Print(builder, Model.DelayLoadDirectoryTable, Model.SectionTable);

            // Named Sections
            Print(builder, Model.BaseRelocationTable, Model.SectionTable);
            Print(builder, Model.DebugTable);
        }

        private static void Print(StringBuilder builder, Data.Models.MSDOS.ExecutableHeader header)
        {
            builder.AppendLine("  MS-DOS Stub Header Information:");
            builder.AppendLine("  -------------------------");
            builder.AppendLine(header.Magic, "  Magic number");
            builder.AppendLine(header.LastPageBytes, "  Last page bytes");
            builder.AppendLine(header.Pages, "  Pages");
            builder.AppendLine(header.RelocationItems, "  Relocation items");
            builder.AppendLine(header.HeaderParagraphSize, "  Header paragraph size");
            builder.AppendLine(header.MinimumExtraParagraphs, "  Minimum extra paragraphs");
            builder.AppendLine(header.MaximumExtraParagraphs, "  Maximum extra paragraphs");
            builder.AppendLine(header.InitialSSValue, "  Initial SS value");
            builder.AppendLine(header.InitialSPValue, "  Initial SP value");
            builder.AppendLine(header.Checksum, "  Checksum");
            builder.AppendLine(header.InitialIPValue, "  Initial IP value");
            builder.AppendLine(header.InitialCSValue, "  Initial CS value");
            builder.AppendLine(header.RelocationTableAddr, "  Relocation table address");
            builder.AppendLine(header.OverlayNumber, "  Overlay number");
            builder.AppendLine();

            builder.AppendLine("  MS-DOS Stub Extended Header Information:");
            builder.AppendLine("  -------------------------");
            builder.AppendLine(header.Reserved1, "  Reserved words");
            builder.AppendLine(header.OEMIdentifier, "  OEM identifier");
            builder.AppendLine(header.OEMInformation, "  OEM information");
            builder.AppendLine(header.Reserved2, "  Reserved words");
            builder.AppendLine(header.NewExeHeaderAddr, "  New EXE header address");
            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, string? signature, FileHeader header)
        {
            builder.AppendLine("  File Header Information:");
            builder.AppendLine("  -------------------------");
            builder.AppendLine(signature, "  Signature");
            builder.AppendLine($"  Machine: {header.Machine} (0x{header.Machine:X})");
            builder.AppendLine(header.NumberOfSections, "  Number of sections");
            builder.AppendLine(header.TimeDateStamp, "  Time/Date stamp");
            builder.AppendLine(header.PointerToSymbolTable, "  Pointer to symbol table");
            builder.AppendLine(header.NumberOfSymbols, "  Number of symbols");
            builder.AppendLine(header.SizeOfOptionalHeader, "  Size of optional header");
            builder.AppendLine($"  Characteristics: {header.Characteristics} (0x{header.Characteristics:X})");
            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.OptionalHeader header, SectionHeader[] table)
        {
            builder.AppendLine("  Optional Header Information:");
            builder.AppendLine("  -------------------------");
            builder.AppendLine($"  Magic: {header.Magic} (0x{header.Magic:X})");
            builder.AppendLine(header.MajorLinkerVersion, "  Major linker version");
            builder.AppendLine(header.MinorLinkerVersion, "  Minor linker version");
            builder.AppendLine(header.SizeOfCode, "  Size of code section");
            builder.AppendLine(header.SizeOfInitializedData, "  Size of initialized data");
            builder.AppendLine(header.SizeOfUninitializedData, "  Size of uninitialized data");
            builder.AppendLine(header.AddressOfEntryPoint, "  Address of entry point");
            builder.AppendLine(header.BaseOfCode, "  Base of code");
            if (header.Magic == OptionalHeaderMagicNumber.PE32)
                builder.AppendLine(header.BaseOfData, "  Base of data");

            builder.AppendLine(header.ImageBase, "  Image base");
            builder.AppendLine(header.SectionAlignment, "  Section alignment");
            builder.AppendLine(header.FileAlignment, "  File alignment");
            builder.AppendLine(header.MajorOperatingSystemVersion, "  Major operating system version");
            builder.AppendLine(header.MinorOperatingSystemVersion, "  Minor operating system version");
            builder.AppendLine(header.MajorImageVersion, "  Major image version");
            builder.AppendLine(header.MinorImageVersion, "  Minor image version");
            builder.AppendLine(header.MajorSubsystemVersion, "  Major subsystem version");
            builder.AppendLine(header.MinorSubsystemVersion, "  Minor subsystem version");
            builder.AppendLine(header.Win32VersionValue, "  Win32 version value");
            builder.AppendLine(header.SizeOfImage, "  Size of image");
            builder.AppendLine(header.SizeOfHeaders, "  Size of headers");
            builder.AppendLine(header.CheckSum, "  Checksum");
            builder.AppendLine($"  Subsystem: {header.Subsystem} (0x{header.Subsystem:X})");
            builder.AppendLine($"  DLL characteristics: {header.DllCharacteristics} (0x{header.DllCharacteristics:X})");
            builder.AppendLine(header.SizeOfStackReserve, "  Size of stack reserve");
            builder.AppendLine(header.SizeOfStackCommit, "  Size of stack commit");
            builder.AppendLine(header.SizeOfHeapReserve, "  Size of heap reserve");
            builder.AppendLine(header.SizeOfHeapCommit, "  Size of heap commit");
            builder.AppendLine(header.LoaderFlags, "  Loader flags");
            builder.AppendLine(header.NumberOfRvaAndSizes, "  Number of data-directory entries");

            if (header.ExportTable is not null)
            {
                builder.AppendLine("    Export Table (1)");
                builder.AppendLine(header.ExportTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.ExportTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.ExportTable.Size, "      Size");
            }

            if (header.ImportTable is not null)
            {
                builder.AppendLine("    Import Table (2)");
                builder.AppendLine(header.ImportTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.ImportTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.ImportTable.Size, "      Size");
            }

            if (header.ResourceTable is not null)
            {
                builder.AppendLine("    Resource Table (3)");
                builder.AppendLine(header.ResourceTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.ResourceTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.ResourceTable.Size, "      Size");
            }

            if (header.ExceptionTable is not null)
            {
                builder.AppendLine("    Exception Table (4)");
                builder.AppendLine(header.ExceptionTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.ExceptionTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.ExceptionTable.Size, "      Size");
            }

            if (header.CertificateTable is not null)
            {
                builder.AppendLine("    Certificate Table (5)");
                builder.AppendLine("      Virtual address: N/A");
                builder.AppendLine(header.CertificateTable.VirtualAddress, "      Physical address");
                builder.AppendLine(header.CertificateTable.Size, "      Size");
            }

            if (header.BaseRelocationTable is not null)
            {
                builder.AppendLine("    Base Relocation Table (6)");
                builder.AppendLine(header.BaseRelocationTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.BaseRelocationTable.Size, "      Size");
            }

            if (header.Debug is not null)
            {
                builder.AppendLine("    Debug Table (7)");
                builder.AppendLine(header.Debug.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.Debug.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.Debug.Size, "      Size");
            }

            if (header.NumberOfRvaAndSizes >= 8)
            {
                builder.AppendLine("    Architecture Table (8)");
                builder.AppendLine("      Virtual address: 0 (0x00000000)");
                builder.AppendLine("      Physical address: 0 (0x00000000)");
                builder.AppendLine("      Size: 0 (0x00000000)");
            }

            if (header.GlobalPtr is not null)
            {
                builder.AppendLine("    Global Pointer Register (9)");
                builder.AppendLine(header.GlobalPtr.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.GlobalPtr.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.GlobalPtr.Size, "      Size");
            }

            if (header.ThreadLocalStorageTable is not null)
            {
                builder.AppendLine("    Thread Local Storage (TLS) Table (10)");
                builder.AppendLine(header.ThreadLocalStorageTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.ThreadLocalStorageTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.ThreadLocalStorageTable.Size, "      Size");
            }

            if (header.LoadConfigTable is not null)
            {
                builder.AppendLine("    Load Config Table (11)");
                builder.AppendLine(header.LoadConfigTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.LoadConfigTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.LoadConfigTable.Size, "      Size");
            }

            if (header.BoundImport is not null)
            {
                builder.AppendLine("    Bound Import Table (12)");
                builder.AppendLine(header.BoundImport.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.BoundImport.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.BoundImport.Size, "      Size");
            }

            if (header.ImportAddressTable is not null)
            {
                builder.AppendLine("    Import Address Table (13)");
                builder.AppendLine(header.ImportAddressTable.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.ImportAddressTable.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.ImportAddressTable.Size, "      Size");
            }

            if (header.DelayImportDescriptor is not null)
            {
                builder.AppendLine("    Delay Import Descriptor (14)");
                builder.AppendLine(header.DelayImportDescriptor.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.DelayImportDescriptor.Size, "      Size");
            }

            if (header.CLRRuntimeHeader is not null)
            {
                builder.AppendLine("    CLR Runtime Header (15)");
                builder.AppendLine(header.CLRRuntimeHeader.VirtualAddress, "      Virtual address");
                builder.AppendLine(header.CLRRuntimeHeader.VirtualAddress.ConvertVirtualAddress(table), "      Physical address");
                builder.AppendLine(header.CLRRuntimeHeader.Size, "      Size");
            }

            if (header.NumberOfRvaAndSizes >= 16)
            {
                builder.AppendLine("    Reserved (16)");
                builder.AppendLine("      Virtual address: 0 (0x00000000)");
                builder.AppendLine("      Physical address: 0 (0x00000000)");
                builder.AppendLine("      Size: 0 (0x00000000)");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, SectionHeader[] entries)
        {
            builder.AppendLine("  Section Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries is null || entries.Length == 0)
            {
                builder.AppendLine("  No section table items");
                builder.AppendLine();
                return;
            }

            for (int i = 0; i < entries!.Length; i++)
            {
                var entry = entries[i];

                builder.AppendLine($"  Section Table Entry {i}");
                builder.AppendLine(entry.Name, "    Name", Encoding.ASCII);
                builder.AppendLine(entry.VirtualSize, "    Virtual size");
                builder.AppendLine(entry.VirtualAddress, "    Virtual address");
                builder.AppendLine(entry.VirtualAddress.ConvertVirtualAddress(entries), "    Physical address");
                builder.AppendLine(entry.SizeOfRawData, "    Size of raw data");
                builder.AppendLine(entry.PointerToRawData, "    Pointer to raw data");
                builder.AppendLine(entry.PointerToRelocations, "    Pointer to relocations");
                builder.AppendLine(entry.PointerToLinenumbers, "    Pointer to linenumbers");
                builder.AppendLine(entry.NumberOfRelocations, "    Number of relocations");
                builder.AppendLine(entry.NumberOfLinenumbers, "    Number of linenumbers");
                builder.AppendLine($"    Characteristics: {entry.Characteristics} (0x{entry.Characteristics:X})");
                // TODO: Add COFFRelocations
                // TODO: Add COFFLineNumbers
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, BaseEntry[]? entries)
        {
            builder.AppendLine("  Symbol Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries is null || entries.Length == 0)
            {
                builder.AppendLine("  No symbol table items");
                builder.AppendLine();
                return;
            }

            for (int i = 0; i < entries.Length; i++)
            {
                var entry = entries[i];

                switch (entry)
                {
                    case StandardRecord item: Print(builder, item, i); break;
                    case FunctionDefinition item: Print(builder, item, i); break;
                    case Descriptor item: Print(builder, item, i); break;
                    case WeakExternal item: Print(builder, item, i); break;
                    case FileRecord item: Print(builder, item, i); break;
                    case SectionDefinition item: Print(builder, item, i); break;
                    case CLRTokenDefinition item: Print(builder, item, i); break;
                    default: break;
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, StandardRecord entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (Standard Record)");
            if (entry.ShortName is not null)
            {
                builder.AppendLine(entry.ShortName, "    Short name", Encoding.ASCII);
            }
            else
            {
                builder.AppendLine(entry.Zeroes, "    Zeroes");
                builder.AppendLine(entry.Offset, "    Offset");
            }

            builder.AppendLine(entry.Value, "    Value");
            builder.AppendLine($"    Section number: {entry.SectionNumber} (0x{entry.SectionNumber:X})");
            builder.AppendLine($"    Symbol type: {entry.SymbolType} (0x{entry.SymbolType:X})");
            builder.AppendLine($"    Storage class: {entry.StorageClass} (0x{entry.StorageClass:X})");
            builder.AppendLine(entry.NumberOfAuxSymbols, "    Number of aux symbols");
        }

        private static void Print(StringBuilder builder, FunctionDefinition entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (Function Definition)");
            builder.AppendLine(entry.TagIndex, "    Tag index");
            builder.AppendLine(entry.TotalSize, "    Total size");
            builder.AppendLine(entry.PointerToLinenumber, "    Pointer to linenumber");
            builder.AppendLine(entry.PointerToNextFunction, "    Pointer to next function");
            builder.AppendLine(entry.Unused, "    Unused");
        }

        private static void Print(StringBuilder builder, Descriptor entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (.bf and .ef Symbol)");
            builder.AppendLine(entry.Unused1, "    Unused");
            builder.AppendLine(entry.Linenumber, "    Linenumber");
            builder.AppendLine(entry.Unused2, "    Unused");
            builder.AppendLine(entry.PointerToNextFunction, "    Pointer to next function");
            builder.AppendLine(entry.Unused3, "    Unused");
        }

        private static void Print(StringBuilder builder, WeakExternal entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (Weak External)");
            builder.AppendLine(entry.TagIndex, "    Tag index");
            builder.AppendLine(entry.Characteristics, "    Characteristics");
            builder.AppendLine(entry.Unused, "    Unused");
        }

        private static void Print(StringBuilder builder, FileRecord entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (File)");
            builder.AppendLine(entry.FileName, "    File name", Encoding.ASCII);
        }

        private static void Print(StringBuilder builder, SectionDefinition entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (Section Defintion)");
            builder.AppendLine(entry.Length, "    Length");
            builder.AppendLine(entry.NumberOfRelocations, "    Number of relocations");
            builder.AppendLine(entry.NumberOfLinenumbers, "    Number of linenumbers");
            builder.AppendLine(entry.CheckSum, "    Checksum");
            builder.AppendLine(entry.Number, "    Number");
            builder.AppendLine(entry.Selection, "    Selection");
            builder.AppendLine(entry.Unused, "    Unused");
        }

        private static void Print(StringBuilder builder, CLRTokenDefinition entry, int i)
        {
            builder.AppendLine($"  Symbol Table Entry {i} (CLR Token Defintion)");
            builder.AppendLine(entry.AuxType, "    Aux type");
            builder.AppendLine(entry.Reserved1, "    Reserved");
            builder.AppendLine(entry.SymbolTableIndex, "    Symbol table index");
            builder.AppendLine(entry.Reserved2, "    Reserved");
        }

        private static void Print(StringBuilder builder, Data.Models.COFF.StringTable? stringTable)
        {
            builder.AppendLine("  String Table Information:");
            builder.AppendLine("  -------------------------");
            if (stringTable?.Strings is null || stringTable.Strings.Length == 0)
            {
                builder.AppendLine("  No string table items");
                builder.AppendLine();
                return;
            }

            builder.AppendLine(stringTable.TotalSize, "  Total size");
            for (int i = 0; i < stringTable.Strings.Length; i++)
            {
                string? entry = stringTable.Strings[i];
                builder.AppendLine($"  String Table Entry {i})");
                builder.AppendLine(entry, "    Value");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.AttributeCertificate.Entry[]? entries)
        {
            builder.AppendLine("  Attribute Certificate Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries is null || entries.Length == 0)
            {
                builder.AppendLine("  No attribute certificate table items");
                builder.AppendLine();
                return;
            }

            // Create the deserializer
            var deserializer = new Serialization.Readers.AbstractSyntaxNotationOne();

            for (int i = 0; i < entries.Length; i++)
            {
                var entry = entries[i];

                builder.AppendLine($"  Attribute Certificate Table Entry {i}");
                builder.AppendLine(entry.Length, "    Length");
                builder.AppendLine($"    Revision: {entry.Revision} (0x{entry.Revision:X})");
                builder.AppendLine($"    Certificate type: {entry.CertificateType} (0x{entry.CertificateType:X})");
                builder.AppendLine();
                if (entry.CertificateType == WindowsCertificateType.WIN_CERT_TYPE_PKCS_SIGNED_DATA)
                {
                    builder.AppendLine("    Certificate Data [Formatted]");
                    builder.AppendLine("    -------------------------");
                    if (entry.Certificate is null)
                    {
                        builder.AppendLine("    INVALID DATA FOUND");
                    }
                    else
                    {
                        var topLevelValues = deserializer.Deserialize(entry.Certificate, 0);
                        if (topLevelValues is null)
                        {
                            builder.AppendLine("    INVALID DATA FOUND");
                            builder.AppendLine(entry.Certificate, "    Raw data");
                        }
                        else
                        {
                            foreach (Data.Models.ASN1.TypeLengthValue tlv in topLevelValues)
                            {
                                string tlvString = tlv.Format(paddingLevel: 4);
                                builder.AppendLine(tlvString);
                            }
                        }
                    }
                }
                else
                {
                    builder.AppendLine("    Certificate Data [Binary]");
                    builder.AppendLine("  -------------------------");
                    try
                    {
                        builder.AppendLine(entry.Certificate, "    Raw data");
                    }
                    catch
                    {
                        builder.AppendLine("    [DATA TOO LARGE TO FORMAT]");
                    }
                }

                builder.AppendLine();
            }
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.DelayLoad.DirectoryTable? table, SectionHeader[] sections)
        {
            builder.AppendLine("  Delay-Load Directory Table Information:");
            builder.AppendLine("  -------------------------");
            if (table is null)
            {
                builder.AppendLine("  No delay-load directory table items");
                builder.AppendLine();
                return;
            }

            builder.AppendLine(table.Attributes, "  Attributes");
            builder.AppendLine(table.NameRVA, "  Name RVA");
            builder.AppendLine(table.NameRVA.ConvertVirtualAddress(sections), "  Name physical address");
            builder.AppendLine(table.ModuleHandle, "  Module handle");
            builder.AppendLine(table.DelayImportAddressTable, "  Delay import address table RVA");
            builder.AppendLine(table.DelayImportAddressTable.ConvertVirtualAddress(sections), "  Delay import address table physical address");
            builder.AppendLine(table.DelayImportNameTable, "  Delay import name table RVA");
            builder.AppendLine(table.DelayImportNameTable.ConvertVirtualAddress(sections), "  Delay import name table physical address");
            builder.AppendLine(table.BoundDelayImportTable, "  Bound delay import table RVA");
            builder.AppendLine(table.BoundDelayImportTable.ConvertVirtualAddress(sections), "  Bound delay import table physical address");
            builder.AppendLine(table.UnloadDelayImportTable, "  Unload delay import table RVA");
            builder.AppendLine(table.UnloadDelayImportTable.ConvertVirtualAddress(sections), "  Unload delay import table physical address");
            builder.AppendLine(table.TimeStamp, "  Timestamp");
            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.BaseRelocation.Block[]? entries, SectionHeader[] sections)
        {
            builder.AppendLine("  Base Relocation Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries is null || entries.Length == 0)
            {
                builder.AppendLine("  No base relocation table items");
                builder.AppendLine();
                return;
            }

            for (int i = 0; i < entries.Length; i++)
            {
                var baseRelocationTableEntry = entries[i];

                builder.AppendLine($"  Base Relocation Table Entry {i}");
                builder.AppendLine(baseRelocationTableEntry.PageRVA, "    Page RVA");
                builder.AppendLine(baseRelocationTableEntry.PageRVA.ConvertVirtualAddress(sections), "    Page physical address");
                builder.AppendLine(baseRelocationTableEntry.BlockSize, "    Block size");
                builder.AppendLine();

                builder.AppendLine($"    Base Relocation Table {i} Type and Offset Information:");
                builder.AppendLine("    -------------------------");
                if (baseRelocationTableEntry.TypeOffsetFieldEntries.Length == 0)
                {
                    builder.AppendLine("    No base relocation table type and offset entries");
                    builder.AppendLine();
                    continue;
                }

                for (int j = 0; j < baseRelocationTableEntry.TypeOffsetFieldEntries.Length; j++)
                {
                    var typeOffsetFieldEntry = baseRelocationTableEntry.TypeOffsetFieldEntries[j];

                    builder.AppendLine($"    Type and Offset Entry {j}");
                    builder.AppendLine($"      Type: {typeOffsetFieldEntry.BaseRelocationType} (0x{typeOffsetFieldEntry.BaseRelocationType:X})");
                    builder.AppendLine(typeOffsetFieldEntry.Offset, "      Offset");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.DebugData.Table? table)
        {
            builder.AppendLine("  Debug Table Information:");
            builder.AppendLine("  -------------------------");
            if (table?.DebugDirectoryTable is null || table.DebugDirectoryTable.Length == 0)
            {
                builder.AppendLine("  No debug table items");
                builder.AppendLine();
                return;
            }

            for (int i = 0; i < table.DebugDirectoryTable.Length; i++)
            {
                var entry = table.DebugDirectoryTable[i];

                builder.AppendLine($"  Debug Directory Table Entry {i}");
                builder.AppendLine(entry.Characteristics, "    Characteristics");
                builder.AppendLine(entry.TimeDateStamp, "    Time/Date stamp");
                builder.AppendLine(entry.MajorVersion, "    Major version");
                builder.AppendLine(entry.MinorVersion, "    Minor version");
                builder.AppendLine($"    Debug type: {entry.DebugType} (0x{entry.DebugType:X})");
                builder.AppendLine(entry.SizeOfData, "    Size of data");
                builder.AppendLine(entry.AddressOfRawData, "    Address of raw data");
                builder.AppendLine(entry.PointerToRawData, "    Pointer to raw data");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Export.DirectoryTable? table, SectionHeader[] sections)
        {
            builder.AppendLine(value: "  Export Directory Table Information:");
            builder.AppendLine("  -------------------------");
            if (table is null)
            {
                builder.AppendLine("  No export directory table");
            }
            else
            {
                builder.AppendLine(table.ExportFlags, "  Export flags");
                builder.AppendLine(table.TimeDateStamp, "  Time/Date stamp");
                builder.AppendLine(table.MajorVersion, "  Major version");
                builder.AppendLine(table.MinorVersion, "  Minor version");
                builder.AppendLine(table.NameRVA, "  Name RVA");
                builder.AppendLine(table.NameRVA.ConvertVirtualAddress(sections), "  Name physical address");
                builder.AppendLine(table.Name, "  Name");
                builder.AppendLine(table.OrdinalBase, "  Ordinal base");
                builder.AppendLine(table.AddressTableEntries, "  Address table entries");
                builder.AppendLine(table.NumberOfNamePointers, "  Number of name pointers");
                builder.AppendLine(table.ExportAddressTableRVA, "  Export address table RVA");
                builder.AppendLine(table.ExportAddressTableRVA.ConvertVirtualAddress(sections), "  Export address table physical address");
                builder.AppendLine(table.NamePointerRVA, "  Name pointer table RVA");
                builder.AppendLine(table.NamePointerRVA.ConvertVirtualAddress(sections), "  Name pointer table physical address");
                builder.AppendLine(table.OrdinalTableRVA, "    Ordinal table RVA");
                builder.AppendLine(table.OrdinalTableRVA.ConvertVirtualAddress(sections), "  Ordinal table physical address");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Export.AddressTableEntry[]? table, SectionHeader[] sections)
        {
            builder.AppendLine("  Export Address Table Information:");
            builder.AppendLine("  -------------------------");
            if (table is null || table.Length == 0)
            {
                builder.AppendLine("  No export address table items");
            }
            else
            {
                for (int i = 0; i < table.Length; i++)
                {
                    var entry = table[i];

                    builder.AppendLine($"  Export Address Table Entry {i}");
                    builder.AppendLine(entry.ExportRVA, "    Export / Forwarder RVA");
                    builder.AppendLine(entry.ExportRVA.ConvertVirtualAddress(sections), "    Export / Forwarder physical address");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Export.NamePointerTable? table)
        {
            builder.AppendLine("  Export Name Pointer Table Information:");
            builder.AppendLine("  -------------------------");
            if (table?.Pointers is null || table.Pointers.Length == 0)
            {
                builder.AppendLine("  No export name pointer table items");
            }
            else
            {
                for (int i = 0; i < table.Pointers.Length; i++)
                {
                    var entry = table.Pointers[i];

                    builder.AppendLine($"  Export Name Pointer Table Entry {i}");
                    builder.AppendLine(entry, "    Pointer");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Export.OrdinalTable? table)
        {
            builder.AppendLine("  Export Ordinal Table Information:");
            builder.AppendLine("  -------------------------");
            if (table?.Indexes is null || table.Indexes.Length == 0)
            {
                builder.AppendLine("  No export ordinal table items");
            }
            else
            {
                for (int i = 0; i < table.Indexes.Length; i++)
                {
                    var entry = table.Indexes[i];

                    builder.AppendLine($"  Export Ordinal Table Entry {i}");
                    builder.AppendLine(entry, "    Index");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Export.NameTable? table)
        {
            builder.AppendLine("  Export Name Table Information:");
            builder.AppendLine("  -------------------------");
            if (table?.Strings is null || table.Strings.Length == 0)
            {
                builder.AppendLine("  No export name table items");
            }
            else
            {
                for (int i = 0; i < table.Strings.Length; i++)
                {
                    var entry = table.Strings[i];

                    builder.AppendLine($"  Export Name Table Entry {i}");
                    builder.AppendLine(entry, "    String");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Import.DirectoryTableEntry[]? table, SectionHeader[] sections)
        {
            builder.AppendLine("  Import Directory Table Information:");
            builder.AppendLine("  -------------------------");
            if (table is null || table.Length == 0)
            {
                builder.AppendLine("  No import directory table items");
            }
            else
            {
                for (int i = 0; i < table.Length; i++)
                {
                    var entry = table[i];

                    builder.AppendLine($"  Import Directory Table Entry {i}");
                    builder.AppendLine(entry.ImportLookupTableRVA, "    Import lookup table RVA");
                    builder.AppendLine(entry.ImportLookupTableRVA.ConvertVirtualAddress(sections), "    Import lookup table physical address");
                    builder.AppendLine(entry.TimeDateStamp, "    Time/Date stamp");
                    builder.AppendLine(entry.ForwarderChain, "    Forwarder chain");
                    builder.AppendLine(entry.NameRVA, "    Name RVA");
                    builder.AppendLine(entry.NameRVA.ConvertVirtualAddress(sections), "    Name physical address");
                    builder.AppendLine(entry.Name, "    Name");
                    builder.AppendLine(entry.ImportAddressTableRVA, "    Import address table RVA");
                    builder.AppendLine(entry.ImportAddressTableRVA.ConvertVirtualAddress(sections), "    Import address table physical address");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Dictionary<int, Data.Models.PortableExecutable.Import.LookupTableEntry[]?>? tables, SectionHeader[] sections)
        {
            builder.AppendLine("  Import Lookup Tables Information:");
            builder.AppendLine("  -------------------------");
            if (tables is null || tables.Count == 0)
            {
                builder.AppendLine("  No import lookup tables");
            }
            else
            {
                foreach (var kvp in tables)
                {
                    int index = kvp.Key;
                    var importLookupTable = kvp.Value;

                    builder.AppendLine();
                    builder.AppendLine($"    Import Lookup Table {index} Information:");
                    builder.AppendLine("    -------------------------");
                    if (importLookupTable is null || importLookupTable.Length == 0)
                    {
                        builder.AppendLine("    No import lookup table items");
                        continue;
                    }

                    for (int i = 0; i < importLookupTable.Length; i++)
                    {
                        var entry = importLookupTable[i];

                        builder.AppendLine($"    Import Lookup Table {index} Entry {i}");
                        builder.AppendLine(entry.OrdinalNameFlag, "      Ordinal/Name flag");
                        if (entry.OrdinalNameFlag)
                        {
                            builder.AppendLine(entry.OrdinalNumber, "      Ordinal number");
                        }
                        else
                        {
                            builder.AppendLine(entry.HintNameTableRVA, "      Hint/Name table RVA");
                            builder.AppendLine(entry.HintNameTableRVA.ConvertVirtualAddress(sections), "      Hint/Name table physical address");
                        }
                    }
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Dictionary<int, Data.Models.PortableExecutable.Import.AddressTableEntry[]?>? tables, SectionHeader[] sections)
        {
            builder.AppendLine("  Import Address Tables Information:");
            builder.AppendLine("  -------------------------");
            if (tables is null || tables.Count == 0)
            {
                builder.AppendLine("  No import address tables");
            }
            else
            {
                foreach (var kvp in tables)
                {
                    int index = kvp.Key;
                    var importAddressTable = kvp.Value;

                    builder.AppendLine();
                    builder.AppendLine($"    Import Address Table {index} Information:");
                    builder.AppendLine("    -------------------------");
                    if (importAddressTable is null || importAddressTable.Length == 0)
                    {
                        builder.AppendLine("    No import address table items");
                        continue;
                    }

                    for (int i = 0; i < importAddressTable.Length; i++)
                    {
                        var entry = importAddressTable[i];

                        builder.AppendLine($"    Import Address Table {index} Entry {i}");
                        builder.AppendLine(entry.OrdinalNameFlag, "      Ordinal/Name flag");
                        if (entry.OrdinalNameFlag)
                        {
                            builder.AppendLine(entry.OrdinalNumber, "      Ordinal number");
                        }
                        else
                        {
                            builder.AppendLine(entry.HintNameTableRVA, "      Hint/Name table RVA");
                            builder.AppendLine(entry.HintNameTableRVA.ConvertVirtualAddress(sections), "      Hint/Name table physical address");
                        }
                    }
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Import.HintNameTableEntry[]? table)
        {
            builder.AppendLine("  Import Hint/Name Table Information:");
            builder.AppendLine("  -------------------------");
            if (table is null || table.Length == 0)
            {
                builder.AppendLine("  No import hint/name table items");
            }
            else
            {
                for (int i = 0; i < table.Length; i++)
                {
                    var entry = table[i];

                    builder.AppendLine($"  Hint/Name Table Entry {i}");
                    builder.AppendLine(entry.Hint, "    Hint");
                    builder.AppendLine(entry.Name, "    Name");
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Resource.DirectoryTable? table, SectionHeader[] sections)
        {
            builder.AppendLine("  Resource Directory Table Information:");
            builder.AppendLine("  -------------------------");
            if (table is null)
            {
                builder.AppendLine("  No resource directory table items");
                builder.AppendLine();
                return;
            }

            Print(builder, table, level: 0, types: [], sections);
            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Resource.DirectoryTable table, int level, List<object> types, SectionHeader[] sections)
        {
            string padding = new(' ', (level + 1) * 2);

            builder.AppendLine(level, $"{padding}Table level");
            builder.AppendLine(table.Characteristics, $"{padding}Characteristics");
            builder.AppendLine(table.TimeDateStamp, $"{padding}Time/Date stamp");
            builder.AppendLine(table.MajorVersion, $"{padding}Major version");
            builder.AppendLine(table.MinorVersion, $"{padding}Minor version");
            builder.AppendLine(table.NumberOfNameEntries, $"{padding}Number of name entries");
            builder.AppendLine(table.NumberOfIDEntries, $"{padding}Number of ID entries");
            builder.AppendLine();

            builder.AppendLine($"{padding}Entries");
            builder.AppendLine($"{padding}-------------------------");
            if (table.NumberOfNameEntries == 0 && table.NumberOfIDEntries == 0)
            {
                builder.AppendLine($"{padding}No entries");
                builder.AppendLine();
            }
            else
            {
                for (int i = 0; i < table.Entries.Length; i++)
                {
                    var entry = table.Entries[i];

                    var newTypes = new List<object>(types ?? []);
                    if (entry.Name?.UnicodeString is not null)
                        newTypes.Add(Encoding.Unicode.GetString(entry.Name.UnicodeString));
                    else
                        newTypes.Add(entry.IntegerID);

                    Print(builder, entry, level + 1, newTypes, sections);
                }
            }
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Resource.DirectoryEntry entry, int level, List<object> types, SectionHeader[] sections)
        {
            string padding = new(' ', (level + 1) * 2);

            builder.AppendLine(level, $"{padding}Item level");
            if (entry.NameOffset != default)
            {
                builder.AppendLine(entry.NameOffset, $"{padding}Name offset");
                builder.AppendLine(entry.Name?.UnicodeString, $"{padding}Name ({entry.Name?.Length ?? 0})", Encoding.Unicode);
            }
            else
            {
                builder.AppendLine(entry.IntegerID, $"{padding}Integer ID");
            }

            if (entry.DataEntry is not null)
                Print(builder, entry.DataEntry, level: level + 1, types, sections);
            else if (entry.Subdirectory is not null)
                Print(builder, entry.Subdirectory, level: level + 1, types, sections);
        }

        private static void Print(StringBuilder builder, Data.Models.PortableExecutable.Resource.DataEntry entry, int level, List<object> types, SectionHeader[] sections)
        {
            string padding = new(' ', (level + 1) * 2);

            // TODO: Use ordered list of base types to determine the shape of the data
            var baseTypes = Array.ConvertAll(types.ToArray(), t => t.ToString());
            builder.AppendLine($"{padding}Base types: {string.Join(", ", baseTypes)}");

            builder.AppendLine(level, $"{padding}Entry level");
            builder.AppendLine(entry.DataRVA, $"{padding}Data RVA");
            builder.AppendLine(entry.DataRVA.ConvertVirtualAddress(sections), $"{padding}Data physical address");
            builder.AppendLine(entry.Size, $"{padding}Size");
            builder.AppendLine(entry.Codepage, $"{padding}Codepage");
            builder.AppendLine(entry.Reserved, $"{padding}Reserved");

            // TODO: Print out per-type data
            if (types is not null && types.Count > 0 && types[0] is uint resourceType)
            {
                switch ((ResourceType)resourceType)
                {
                    case ResourceType.RT_CURSOR:
                        PrintResourceRT_CURSOR(entry, level, builder);
                        break;
                    case ResourceType.RT_BITMAP:
                    case ResourceType.RT_NEWBITMAP:
                        PrintResourceRT_BITMAP(entry, level, builder);
                        break;
                    case ResourceType.RT_ICON:
                        PrintResourceRT_ICON(entry, level, builder);
                        break;
                    case ResourceType.RT_MENU:
                    case ResourceType.RT_NEWMENU:
                        PrintResourceRT_MENU(entry, level, builder);
                        break;
                    case ResourceType.RT_DIALOG:
                    case ResourceType.RT_NEWDIALOG:
                        PrintResourceRT_DIALOG(entry, level, builder);
                        break;
                    case ResourceType.RT_STRING:
                        PrintResourceRT_STRING(entry, level, builder);
                        break;
                    case ResourceType.RT_FONTDIR:
                        PrintResourceRT_FONTDIR(entry, level, builder);
                        break;
                    case ResourceType.RT_FONT:
                        PrintResourceRT_FONT(entry, level, builder);
                        break;
                    case ResourceType.RT_ACCELERATOR:
                        PrintResourceRT_ACCELERATOR(entry, level, builder);
                        break;
                    case ResourceType.RT_RCDATA:
                        PrintResourceRT_RCDATA(entry, level, builder);
                        break;
                    case ResourceType.RT_MESSAGETABLE:
                        PrintResourceRT_MESSAGETABLE(entry, level, builder);
                        break;
                    case ResourceType.RT_GROUP_CURSOR:
                        PrintResourceRT_GROUP_CURSOR(entry, level, builder);
                        break;
                    case ResourceType.RT_GROUP_ICON:
                        PrintResourceRT_GROUP_ICON(entry, level, builder);
                        break;
                    case ResourceType.RT_VERSION:
                        PrintResourceRT_VERSION(entry, level, builder);
                        break;
                    case ResourceType.RT_DLGINCLUDE:
                        PrintResourceRT_DLGINCLUDE(entry, level, builder);
                        break;
                    case ResourceType.RT_PLUGPLAY:
                        PrintResourceRT_PLUGPLAY(entry, level, builder);
                        break;
                    case ResourceType.RT_VXD:
                        PrintResourceRT_VXD(entry, level, builder);
                        break;
                    case ResourceType.RT_ANICURSOR:
                        PrintResourceRT_ANICURSOR(entry, level, builder);
                        break;
                    case ResourceType.RT_ANIICON:
                        PrintResourceRT_ANIICON(entry, level, builder);
                        break;
                    case ResourceType.RT_HTML:
                        PrintResourceRT_HTML(entry, level, builder);
                        break;
                    case ResourceType.RT_MANIFEST:
                        PrintResourceRT_MANIFEST(entry, level, builder);
                        break;

                    // Bitflag, ignore
                    case ResourceType.RT_NEWRESOURCE:
                        break;

                    // Error state, ignore
                    case ResourceType.RT_ERROR:
                        PrintResourceUNKNOWN(entry, level, types[0], builder);
                        break;

                    default:
                        PrintResourceUNKNOWN(entry, level, types[0], builder);
                        break;
                }
            }
            else if (types is not null && types.Count > 0 && types[0] is string resourceString)
            {
                PrintResourceUNKNOWN(entry, level, types[0], builder);
            }

            builder.AppendLine();
        }

        private static void PrintResourceRT_CURSOR(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Hardware-dependent cursor resource found, not parsed yet");
        }

        private static void PrintResourceRT_BITMAP(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Bitmap resource found, not parsed yet");
        }

        private static void PrintResourceRT_ICON(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Hardware-dependent icon resource found, not parsed yet");
        }

        private static void PrintResourceRT_MENU(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            MenuResource? menu = null;
            try { menu = Serialization.Readers.PortableExecutable.ParseMenuResource(entry.Data); } catch { }

            if (menu is null)
            {
                builder.AppendLine($"{padding}Menu resource found, but malformed");
                return;
            }

            if (menu.MenuHeader is NormalMenuHeader normalMenuHeader)
            {
                builder.AppendLine(normalMenuHeader.Version, $"{padding}Version");
                builder.AppendLine(normalMenuHeader.HeaderSize, $"{padding}Header size");
            }
            else if (menu.MenuHeader is MenuHeaderExtended menuHeaderExtended)
            {
                builder.AppendLine(menuHeaderExtended.Version, $"{padding}Version");
                builder.AppendLine(menuHeaderExtended.Offset, $"{padding}Offset");
                builder.AppendLine(menuHeaderExtended.HelpID, $"{padding}Help ID");
            }
            else
            {
                builder.AppendLine($"{padding}Menu header found, but malformed");
            }

            builder.AppendLine();

            builder.AppendLine($"{padding}Menu items");
            builder.AppendLine($"{padding}-------------------------");
            if (menu.MenuItems.Length == 0)
            {
                builder.AppendLine($"{padding}No menu items");
                return;
            }

            for (int i = 0; i < menu.MenuItems.Length; i++)
            {
                var menuItem = menu.MenuItems[i];
                builder.AppendLine($"{padding}Menu item {i}");
                if (menuItem is null)
                {
                    builder.AppendLine($"{padding}  [NULL]");
                    continue;
                }

                if (menuItem is NormalMenuItem normalMenuItem)
                {
                    builder.AppendLine($"{padding}  Resource info: {normalMenuItem.NormalResInfo} (0x{normalMenuItem.NormalResInfo:X})");
                    builder.AppendLine(normalMenuItem.NormalMenuText, $"{padding}  Menu text");
                }
                else if (menuItem is PopupMenuItem popupMenuItem)
                {
                    builder.AppendLine($"{padding}  Item type: {popupMenuItem.PopupItemType} (0x{popupMenuItem.PopupItemType:X})");
                    builder.AppendLine($"{padding}  State: {popupMenuItem.PopupState} (0x{popupMenuItem.PopupState:X})");
                    builder.AppendLine(popupMenuItem.PopupID, $"{padding}  ID");
                    builder.AppendLine($"{padding}  Resource info: {popupMenuItem.PopupResInfo} (0x{popupMenuItem.PopupResInfo:X})");
                    builder.AppendLine(popupMenuItem.PopupMenuText, $"{padding}  Menu text");
                }
                else if (menuItem is MenuItemExtended menuItemExtended)
                {
                    builder.AppendLine($"{padding}  Item type: {menuItemExtended.ItemType} (0x{menuItemExtended.ItemType:X})");
                    builder.AppendLine($"{padding}  State: {menuItemExtended.State} (0x{menuItemExtended.State:X})");
                    builder.AppendLine(menuItemExtended.ID, $"{padding}  ID");
                    builder.AppendLine($"{padding}  Flags: {menuItemExtended.Flags} (0x{menuItemExtended.Flags:X})");
                    builder.AppendLine(menuItemExtended.MenuText, $"{padding}  Menu text");
                }
                else
                {
                    builder.AppendLine($"{padding}  Menu item found, but malformed");
                }
            }
        }

        private static void PrintResourceRT_DIALOG(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            DialogBoxResource? dialogBox = null;
            try { dialogBox = Serialization.Readers.PortableExecutable.ParseDialogBoxResource(entry.Data); } catch { }

            if (dialogBox is null)
            {
                builder.AppendLine($"{padding}Dialog box resource found, but malformed");
                return;
            }

            if (dialogBox.DialogTemplate is not null)
            {
                builder.AppendLine($"{padding}Style: {dialogBox.DialogTemplate.Style} (0x{dialogBox.DialogTemplate.Style:X})");
                builder.AppendLine($"{padding}Extended style: {dialogBox.DialogTemplate.ExtendedStyle} (0x{dialogBox.DialogTemplate.ExtendedStyle:X})");
                builder.AppendLine(dialogBox.DialogTemplate.ItemCount, $"{padding}Item count");
                builder.AppendLine(dialogBox.DialogTemplate.PositionX, $"{padding}X-coordinate of upper-left corner");
                builder.AppendLine(dialogBox.DialogTemplate.PositionY, $"{padding}Y-coordinate of upper-left corner");
                builder.AppendLine(dialogBox.DialogTemplate.WidthX, $"{padding}Width of the dialog box");
                builder.AppendLine(dialogBox.DialogTemplate.HeightY, $"{padding}Height of the dialog box");
                builder.AppendLine(dialogBox.DialogTemplate.MenuResource, $"{padding}Menu resource");
                builder.AppendLine(dialogBox.DialogTemplate.MenuResourceOrdinal, $"{padding}Menu resource ordinal");
                builder.AppendLine(dialogBox.DialogTemplate.ClassResource, $"{padding}Class resource");
                builder.AppendLine(dialogBox.DialogTemplate.ClassResourceOrdinal, $"{padding}Class resource ordinal");
                builder.AppendLine(dialogBox.DialogTemplate.TitleResource, $"{padding}Title resource");
                builder.AppendLine(dialogBox.DialogTemplate.PointSizeValue, $"{padding}Point size value");
                builder.AppendLine(dialogBox.DialogTemplate.Typeface, $"{padding}Typeface");
                builder.AppendLine();
                builder.AppendLine($"{padding}Dialog item templates");
                builder.AppendLine($"{padding}-------------------------");
                if (dialogBox.DialogTemplate.ItemCount == 0
                    || dialogBox.DialogItemTemplates is null
                    || dialogBox.DialogItemTemplates.Length == 0)
                {
                    builder.AppendLine($"{padding}No dialog item templates");
                    return;
                }

                for (int i = 0; i < dialogBox.DialogItemTemplates.Length; i++)
                {
                    var dialogItemTemplate = dialogBox.DialogItemTemplates[i];
                    builder.AppendLine($"{padding}Dialog item template {i}");
                    if (dialogItemTemplate is null)
                    {
                        builder.AppendLine($"{padding}  [NULL]");
                        continue;
                    }

                    builder.AppendLine($"{padding}  Style: {dialogItemTemplate.Style} (0x{dialogItemTemplate.Style:X})");
                    builder.AppendLine($"{padding}  Extended style: {dialogItemTemplate.ExtendedStyle} (0x{dialogItemTemplate.ExtendedStyle:X})");
                    builder.AppendLine(dialogItemTemplate.PositionX, $"{padding}  X-coordinate of upper-left corner");
                    builder.AppendLine(dialogItemTemplate.PositionY, $"{padding}  Y-coordinate of upper-left corner");
                    builder.AppendLine(dialogItemTemplate.WidthX, $"{padding}  Width of the control");
                    builder.AppendLine(dialogItemTemplate.HeightY, $"{padding}  Height of the control");
                    builder.AppendLine(dialogItemTemplate.ID, $"{padding}  ID");
                    builder.AppendLine(dialogItemTemplate.ClassResource, $"{padding}  Class resource");
                    builder.AppendLine($"{padding}  Class resource ordinal: {dialogItemTemplate.ClassResourceOrdinal} (0x{dialogItemTemplate.ClassResourceOrdinal:X})");
                    builder.AppendLine(dialogItemTemplate.TitleResource, $"{padding}  Title resource");
                    builder.AppendLine(dialogItemTemplate.TitleResourceOrdinal, $"{padding}  Title resource ordinal");
                    builder.AppendLine(dialogItemTemplate.CreationDataSize, $"{padding}  Creation data size");
                    if (dialogItemTemplate.CreationData is not null && dialogItemTemplate.CreationData.Length != 0)
                        builder.AppendLine(dialogItemTemplate.CreationData, $"{padding}  Creation data");
                    else
                        builder.AppendLine($"{padding}  Creation data: [EMPTY]");
                }
            }
            else if (dialogBox.ExtendedDialogTemplate is not null)
            {
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.Version, $"{padding}Version");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.Signature, $"{padding}Signature");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.HelpID, $"{padding}Help ID");
                builder.AppendLine($"{padding}Extended style: {dialogBox.ExtendedDialogTemplate.ExtendedStyle} (0x{dialogBox.ExtendedDialogTemplate.ExtendedStyle:X})");
                builder.AppendLine($"{padding}Style: {dialogBox.ExtendedDialogTemplate.Style} (0x{dialogBox.ExtendedDialogTemplate.Style:X})");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.DialogItems, $"{padding}Item count");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.PositionX, $"{padding}X-coordinate of upper-left corner");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.PositionY, $"{padding}Y-coordinate of upper-left corner");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.WidthX, $"{padding}Width of the dialog box");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.HeightY, $"{padding}Height of the dialog box");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.MenuResource, $"{padding}Menu resource");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.MenuResourceOrdinal, $"{padding}Menu resource ordinal");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.ClassResource, $"{padding}Class resource");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.ClassResourceOrdinal, $"{padding}Class resource ordinal");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.TitleResource, $"{padding}Title resource");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.PointSize, $"{padding}Point size");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.Weight, $"{padding}Weight");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.Italic, $"{padding}Italic");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.CharSet, $"{padding}Character set");
                builder.AppendLine(dialogBox.ExtendedDialogTemplate.Typeface, $"{padding}Typeface");
                builder.AppendLine();
                builder.AppendLine($"{padding}Dialog item templates");
                builder.AppendLine($"{padding}-------------------------");
                if (dialogBox.ExtendedDialogTemplate.DialogItems == 0
                    || dialogBox.ExtendedDialogItemTemplates is null
                    || dialogBox.ExtendedDialogItemTemplates.Length == 0)
                {
                    builder.AppendLine($"{padding}No dialog item templates");
                    return;
                }

                for (int i = 0; i < dialogBox.ExtendedDialogItemTemplates.Length; i++)
                {
                    var dialogItemTemplate = dialogBox.ExtendedDialogItemTemplates[i];
                    builder.AppendLine($"{padding}Dialog item template {i}");
                    if (dialogItemTemplate is null)
                    {
                        builder.AppendLine($"{padding}  [NULL]");
                        continue;
                    }

                    builder.AppendLine(dialogItemTemplate.HelpID, $"{padding}  Help ID");
                    builder.AppendLine($"{padding}  Extended style: {dialogItemTemplate.ExtendedStyle} (0x{dialogItemTemplate.ExtendedStyle:X})");
                    builder.AppendLine($"{padding}  Style: {dialogItemTemplate.Style} (0x{dialogItemTemplate.Style:X})");
                    builder.AppendLine(dialogItemTemplate.PositionX, $"{padding}  X-coordinate of upper-left corner");
                    builder.AppendLine(dialogItemTemplate.PositionY, $"{padding}  Y-coordinate of upper-left corner");
                    builder.AppendLine(dialogItemTemplate.WidthX, $"{padding}  Width of the control");
                    builder.AppendLine(dialogItemTemplate.HeightY, $"{padding}  Height of the control");
                    builder.AppendLine(dialogItemTemplate.ID, $"{padding}  ID");
                    builder.AppendLine(dialogItemTemplate.ClassResource, $"{padding}  Class resource");
                    builder.AppendLine($"{padding}  Class resource ordinal: {dialogItemTemplate.ClassResourceOrdinal} (0x{dialogItemTemplate.ClassResourceOrdinal:X})");
                    builder.AppendLine(dialogItemTemplate.TitleResource, $"{padding}  Title resource");
                    builder.AppendLine(dialogItemTemplate.TitleResourceOrdinal, $"{padding}  Title resource ordinal");
                    builder.AppendLine(dialogItemTemplate.CreationDataSize, $"{padding}  Creation data size");
                    if (dialogItemTemplate.CreationData is not null && dialogItemTemplate.CreationData.Length != 0)
                        builder.AppendLine(dialogItemTemplate.CreationData, $"{padding}  Creation data");
                    else
                        builder.AppendLine($"{padding}  Creation data: [EMPTY]");
                }
            }
            else
            {
                builder.AppendLine($"{padding}Dialog box resource found, but malformed");
            }
        }

        private static void PrintResourceRT_STRING(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            StringTableResource? stringTable = null;
            try { stringTable = Serialization.Readers.PortableExecutable.ParseStringTableResource(entry.Data); } catch { }

            if (stringTable is null)
            {
                builder.AppendLine($"{padding}String table resource found, but malformed");
                return;
            }

            foreach (var kvp in stringTable.Data)
            {
                int index = kvp.Key;
                string? stringValue = kvp.Value;
                builder.AppendLine(stringValue, $"{padding}String entry {index}");
            }
        }

        private static void PrintResourceRT_FONTDIR(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Font directory resource found, not parsed yet");
        }

        private static void PrintResourceRT_FONT(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Font resource found, not parsed yet");
        }

        private static void PrintResourceRT_ACCELERATOR(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            AcceleratorTable? acceleratorTable = null;
            try { acceleratorTable = Serialization.Readers.PortableExecutable.ParseAcceleratorTable(entry.Data); } catch { }

            if (acceleratorTable?.Entries is null)
            {
                builder.AppendLine($"{padding}Accelerator table resource found, but malformed");
                return;
            }

            for (int i = 0; i < acceleratorTable.Entries.Length; i++)
            {
                var acceleratorTableEntry = acceleratorTable.Entries[i];
                builder.AppendLine($"{padding}Accelerator Table Entry {i}:");
                builder.AppendLine($"{padding}  Flags: {acceleratorTableEntry.Flags} (0x{acceleratorTableEntry.Flags:X})");
                builder.AppendLine(acceleratorTableEntry.Ansi, $"{padding}  Ansi");
                builder.AppendLine(acceleratorTableEntry.Id, $"{padding}  Id");
                builder.AppendLine(acceleratorTableEntry.Padding, $"{padding}  Padding");
            }
        }

        private static void PrintResourceRT_RCDATA(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Application-defined resource found, not parsed yet");

            // Then print the data, if needed
            if (entry.Data is null)
            {
                builder.AppendLine($"{padding}Data: [NULL] (This may indicate a very large resource)");
            }
            else
            {
                int offset = 0;
                byte[] magic = entry.Data.ReadBytes(ref offset, Math.Min(entry.Data.Length, 16));

                if (magic.StartsWith([0x4D, 0x5A]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded Executable File]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x4D, 0x53, 0x46, 0x54]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded OLE Library File]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x50, 0x4B, 0x03, 0x04]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded PKZIP file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x50, 0x4B, 0x05, 0x06]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded empty PKZIP file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x50, 0x4B, 0x07, 0x08]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded spanned PKZIP file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith(Data.Models.RAR.Constants.OldSignatureBytes))
                {
                    builder.AppendLine($"{padding}Data: [Embedded RAR file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith(Data.Models.RAR.Constants.NewSignatureBytes))
                {
                    builder.AppendLine($"{padding}Data: [Embedded RAR5 file]"); // TODO: Parse this out and print separately
                }
                else
                {
                    builder.AppendLine(magic, $"{padding}Data");

                    //if (entry.Data is not null)
                    //    builder.AppendLine(entry.Data, $"{padding}Value (Byte Data)");
                    //if (entry.Data is not null)
                    //    builder.AppendLine(Encoding.ASCII.GetString(entry.Data), $"{padding}Value (ASCII)");
                    //if (entry.Data is not null)
                    //    builder.AppendLine(Encoding.UTF8.GetString(entry.Data), $"{padding}Value (UTF-8)");
                    //if (entry.Data is not null)
                    //    builder.AppendLine(Encoding.Unicode.GetString(entry.Data), $"{padding}Value (Unicode)");
                }
            }
        }

        private static void PrintResourceRT_MESSAGETABLE(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            MessageResourceData? messageTable = null;
            try { messageTable = Serialization.Readers.PortableExecutable.ParseMessageResourceData(entry.Data); } catch { }

            if (messageTable is null)
            {
                builder.AppendLine($"{padding}Message resource data found, but malformed");
                return;
            }

            builder.AppendLine(messageTable.NumberOfBlocks, $"{padding}Number of blocks");
            builder.AppendLine();
            builder.AppendLine($"{padding}Message resource blocks");
            builder.AppendLine($"{padding}-------------------------");
            if (messageTable.NumberOfBlocks == 0 || messageTable.Blocks.Length == 0)
            {
                builder.AppendLine($"{padding}No message resource blocks");
            }
            else
            {
                for (int i = 0; i < messageTable.Blocks.Length; i++)
                {
                    var messageResourceBlock = messageTable.Blocks[i];
                    builder.AppendLine($"{padding}Message resource block {i}");
                    if (messageResourceBlock is null)
                    {
                        builder.AppendLine($"{padding}  [NULL]");
                        continue;
                    }

                    builder.AppendLine(messageResourceBlock.LowId, $"{padding}  Low ID");
                    builder.AppendLine(messageResourceBlock.HighId, $"{padding}  High ID");
                    builder.AppendLine(messageResourceBlock.OffsetToEntries, $"{padding}  Offset to entries");
                }
            }

            builder.AppendLine();

            builder.AppendLine($"{padding}Message resource entries");
            builder.AppendLine($"{padding}-------------------------");
            if (messageTable.Entries.Count == 0)
            {
                builder.AppendLine($"{padding}No message resource entries");
            }
            else
            {
                foreach (var kvp in messageTable.Entries)
                {
                    uint index = kvp.Key;
                    var messageResourceEntry = kvp.Value;
                    builder.AppendLine($"{padding}Message resource entry {index}");
                    if (messageResourceEntry is null)
                    {
                        builder.AppendLine($"{padding}  [NULL]");
                        continue;
                    }

                    builder.AppendLine(messageResourceEntry.Length, $"{padding}  Length");
                    builder.AppendLine(messageResourceEntry.Flags, $"{padding}  Flags");
                    builder.AppendLine(messageResourceEntry.Text, $"{padding}  Text");
                }
            }
        }

        private static void PrintResourceRT_GROUP_CURSOR(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Hardware-independent cursor resource found, not parsed yet");
        }

        private static void PrintResourceRT_GROUP_ICON(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Hardware-independent icon resource found, not parsed yet");
        }

        private static void PrintResourceRT_VERSION(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            VersionInfo? versionInfo = null;
            try { versionInfo = Serialization.Readers.PortableExecutable.ParseVersionInfo(entry.Data); } catch { }

            if (versionInfo is null)
            {
                builder.AppendLine($"{padding}Version info resource found, but malformed");
                return;
            }

            builder.AppendLine(versionInfo.Length, $"{padding}Length");
            builder.AppendLine(versionInfo.ValueLength, $"{padding}Value length");
            builder.AppendLine($"{padding}Resource type: {versionInfo.ResourceType} (0x{versionInfo.ResourceType:X})");
            builder.AppendLine(versionInfo.Key, $"{padding}Key");
            if (versionInfo.ValueLength != 0 && versionInfo.Value is not null)
            {
                builder.AppendLine(versionInfo.Value.Signature, $"{padding}[Fixed File Info] Signature");
                builder.AppendLine(versionInfo.Value.StrucVersion, $"{padding}[Fixed File Info] Struct version");
                builder.AppendLine(versionInfo.Value.FileVersionMS, $"{padding}[Fixed File Info] File version (MS)");
                builder.AppendLine(versionInfo.Value.FileVersionLS, $"{padding}[Fixed File Info] File version (LS)");
                builder.AppendLine(versionInfo.Value.ProductVersionMS, $"{padding}[Fixed File Info] Product version (MS)");
                builder.AppendLine(versionInfo.Value.ProductVersionLS, $"{padding}[Fixed File Info] Product version (LS)");
                builder.AppendLine(versionInfo.Value.FileFlagsMask, $"{padding}[Fixed File Info] File flags mask");
                builder.AppendLine($"{padding}[Fixed File Info] File flags: {versionInfo.Value.FileFlags} (0x{versionInfo.Value.FileFlags:X})");
                builder.AppendLine($"{padding}[Fixed File Info] File OS: {versionInfo.Value.FileOS} (0x{versionInfo.Value.FileOS:X})");
                builder.AppendLine($"{padding}[Fixed File Info] Type: {versionInfo.Value.FileType} (0x{versionInfo.Value.FileType:X})");
                builder.AppendLine($"{padding}[Fixed File Info] Subtype: {versionInfo.Value.FileSubtype} (0x{versionInfo.Value.FileSubtype:X})");
                builder.AppendLine(versionInfo.Value.FileDateMS, $"{padding}[Fixed File Info] File date (MS)");
                builder.AppendLine(versionInfo.Value.FileDateLS, $"{padding}[Fixed File Info] File date (LS)");
            }

            if (versionInfo.StringFileInfo is not null)
            {
                builder.AppendLine(versionInfo.StringFileInfo.Length, $"{padding}[String File Info] Length");
                builder.AppendLine(versionInfo.StringFileInfo.ValueLength, $"{padding}[String File Info] Value length");
                builder.AppendLine($"{padding}[String File Info] Resource type: {versionInfo.StringFileInfo.ResourceType} (0x{versionInfo.StringFileInfo.ResourceType:X})");
                builder.AppendLine(versionInfo.StringFileInfo.Key, $"{padding}[String File Info] Key");
                builder.AppendLine($"{padding}Children:");
                builder.AppendLine($"{padding}-------------------------");
                if (versionInfo.StringFileInfo.Children.Length == 0)
                {
                    builder.AppendLine($"{padding}No string file info children");
                }
                else
                {
                    for (int i = 0; i < versionInfo.StringFileInfo.Children.Length; i++)
                    {
                        var stringFileInfoChildEntry = versionInfo.StringFileInfo.Children[i];
                        if (stringFileInfoChildEntry is null)
                        {
                            builder.AppendLine($"{padding}  [String Table {i}] [NULL]");
                            continue;
                        }

                        builder.AppendLine(stringFileInfoChildEntry.Length, $"{padding}  [String Table {i}] Length");
                        builder.AppendLine(stringFileInfoChildEntry.ValueLength, $"{padding}  [String Table {i}] Value length");
                        builder.AppendLine($"{padding}  [String Table {i}] ResourceType: {stringFileInfoChildEntry.ResourceType} (0x{stringFileInfoChildEntry.ResourceType:X})");
                        builder.AppendLine(stringFileInfoChildEntry.Key, $"{padding}  [String Table {i}] Key");
                        builder.AppendLine($"{padding}  [String Table {i}] Children:");
                        builder.AppendLine($"{padding}  -------------------------");
                        if (stringFileInfoChildEntry.Children.Length == 0)
                        {
                            builder.AppendLine($"{padding}  No string table {i} children");
                        }
                        else
                        {
                            for (int j = 0; j < stringFileInfoChildEntry.Children.Length; j++)
                            {
                                var stringDataEntry = stringFileInfoChildEntry.Children[j];
                                if (stringDataEntry is null)
                                {
                                    builder.AppendLine($"{padding}    [String Data {j}] [NULL]");
                                    continue;
                                }

                                builder.AppendLine(stringDataEntry.Length, $"{padding}    [String Data {j}] Length");
                                builder.AppendLine(stringDataEntry.ValueLength, $"{padding}    [String Data {j}] Value length");
                                builder.AppendLine($"{padding}    [String Data {j}] ResourceType: {stringDataEntry.ResourceType} (0x{stringDataEntry.ResourceType:X})");
                                builder.AppendLine(stringDataEntry.Key, $"{padding}    [String Data {j}] Key");
                                builder.AppendLine(stringDataEntry.Value, $"{padding}    [String Data {j}] Value");
                            }
                        }
                    }
                }
            }

            if (versionInfo.VarFileInfo is not null)
            {
                builder.AppendLine(versionInfo.VarFileInfo.Length, $"{padding}[Var File Info] Length");
                builder.AppendLine(versionInfo.VarFileInfo.ValueLength, $"{padding}[Var File Info] Value length");
                builder.AppendLine($"{padding}[Var File Info] Resource type: {versionInfo.VarFileInfo.ResourceType} (0x{versionInfo.VarFileInfo.ResourceType:X})");
                builder.AppendLine(versionInfo.VarFileInfo.Key, $"{padding}[Var File Info] Key");
                builder.AppendLine($"{padding}Children:");
                builder.AppendLine($"{padding}-------------------------");
                if (versionInfo.VarFileInfo.Children.Length == 0)
                {
                    builder.AppendLine($"{padding}No var file info children");
                }
                else
                {
                    for (int i = 0; i < versionInfo.VarFileInfo.Children.Length; i++)
                    {
                        var varFileInfoChildEntry = versionInfo.VarFileInfo.Children[i];
                        if (varFileInfoChildEntry is null)
                        {
                            builder.AppendLine($"{padding}  [String Table {i}] [NULL]");
                            continue;
                        }

                        builder.AppendLine(varFileInfoChildEntry.Length, $"{padding}  [String Table {i}] Length");
                        builder.AppendLine(varFileInfoChildEntry.ValueLength, $"{padding}  [String Table {i}] Value length");
                        builder.AppendLine($"{padding}  [String Table {i}] ResourceType: {varFileInfoChildEntry.ResourceType} (0x{varFileInfoChildEntry.ResourceType:X})");
                        builder.AppendLine(varFileInfoChildEntry.Key, $"{padding}  [String Table {i}] Key");
                        builder.AppendLine(varFileInfoChildEntry.Value, $"{padding}  [String Table {i}] Value");
                    }
                }
            }
        }

        private static void PrintResourceRT_DLGINCLUDE(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}External header resource found, not parsed yet");
        }

        private static void PrintResourceRT_PLUGPLAY(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Plug and Play resource found, not parsed yet");
        }

        private static void PrintResourceRT_VXD(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}VXD found, not parsed yet");
        }

        private static void PrintResourceRT_ANICURSOR(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Animated cursor found, not parsed yet");
        }

        private static void PrintResourceRT_ANIICON(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}Animated icon found, not parsed yet");
        }

        private static void PrintResourceRT_HTML(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);
            builder.AppendLine($"{padding}HTML resource found, not parsed yet");

            //if (entry.Data is not null)
            //    builder.AppendLine(Encoding.ASCII.GetString(entry.Data), $"{padding}Value (ASCII)");
            //if (entry.Data is not null)
            //    builder.AppendLine(Encoding.UTF8.GetString(entry.Data), $"{padding}Value (UTF-8)");
            //if (entry.Data is not null)
            //    builder.AppendLine(Encoding.Unicode.GetString(entry.Data), $"{padding}Value (Unicode)");
        }

        private static void PrintResourceRT_MANIFEST(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            AssemblyManifest? assemblyManifest = null;
            try { assemblyManifest = Serialization.Readers.PortableExecutable.ParseAssemblyManifest(entry.Data); } catch { }

            if (assemblyManifest is null)
            {
                builder.AppendLine($"{padding}Assembly manifest found, but malformed");
                return;
            }

            builder.AppendLine(assemblyManifest.ManifestVersion, $"{padding}Manifest version");
            if (assemblyManifest.AssemblyIdentities is not null && assemblyManifest.AssemblyIdentities.Length > 0)
            {
                for (int i = 0; i < assemblyManifest.AssemblyIdentities.Length; i++)
                {
                    var assemblyIdentity = assemblyManifest.AssemblyIdentities[i];

                    builder.AppendLine(assemblyIdentity.Name, $"{padding}[Assembly Identity {i}] Name");
                    builder.AppendLine(assemblyIdentity.Version, $"{padding}[Assembly Identity {i}] Version");
                    builder.AppendLine(assemblyIdentity.Type, $"{padding}[Assembly Identity {i}] Type");
                    builder.AppendLine(assemblyIdentity.ProcessorArchitecture, $"{padding}[Assembly Identity {i}] Processor architecture");
                    builder.AppendLine(assemblyIdentity.PublicKeyToken, $"{padding}[Assembly Identity {i}] Public key token");
                    builder.AppendLine(assemblyIdentity.Language, $"{padding}[Assembly Identity {i}] Language");
                }
            }

            if (assemblyManifest.Description is not null)
                builder.AppendLine(assemblyManifest.Description.Value, $"{padding}[Assembly Description] Value");

            if (assemblyManifest.COMInterfaceExternalProxyStub is not null && assemblyManifest.COMInterfaceExternalProxyStub.Length > 0)
            {
                for (int i = 0; i < assemblyManifest.COMInterfaceExternalProxyStub.Length; i++)
                {
                    var comInterfaceExternalProxyStub = assemblyManifest.COMInterfaceExternalProxyStub[i];

                    builder.AppendLine(comInterfaceExternalProxyStub.IID, $"{padding}[COM Interface External Proxy Stub {i}] IID");
                    builder.AppendLine(comInterfaceExternalProxyStub.Name, $"{padding}[COM Interface External Proxy Stub {i}] Name");
                    builder.AppendLine(comInterfaceExternalProxyStub.TLBID, $"{padding}[COM Interface External Proxy Stub {i}] TLBID");
                    builder.AppendLine(comInterfaceExternalProxyStub.NumMethods, $"{padding}[COM Interface External Proxy Stub {i}] Number of methods");
                    builder.AppendLine(comInterfaceExternalProxyStub.ProxyStubClsid32, $"{padding}[COM Interface External Proxy Stub {i}] Proxy stub (CLSID32)");
                    builder.AppendLine(comInterfaceExternalProxyStub.BaseInterface, $"{padding}[COM Interface External Proxy Stub {i}] Base interface");
                }
            }

            if (assemblyManifest.Dependency is not null && assemblyManifest.Dependency.Length > 0)
            {
                for (int i = 0; i < assemblyManifest.Dependency.Length; i++)
                {
                    var dependency = assemblyManifest.Dependency[i];
                    if (dependency?.DependentAssembly is not null)
                    {
                        if (dependency.DependentAssembly.AssemblyIdentity is not null)
                        {
                            builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Name, $"{padding}[Dependency {i} Assembly Identity] Name");
                            builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Version, $"{padding}[Dependency {i} Assembly Identity] Version");
                            builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Type, $"{padding}[Dependency {i} Assembly Identity] Type");
                            builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.ProcessorArchitecture, $"{padding}[Dependency {i} Assembly Identity] Processor architecture");
                            builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.PublicKeyToken, $"{padding}[Dependency {i} Assembly Identity] Public key token");
                            builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Language, $"{padding}[Dependency {i} Assembly Identity] Language");
                        }

                        if (dependency.DependentAssembly.BindingRedirect is not null && dependency.DependentAssembly.BindingRedirect.Length > 0)
                        {
                            for (int j = 0; j < dependency.DependentAssembly.BindingRedirect.Length; j++)
                            {
                                var bindingRedirect = dependency.DependentAssembly.BindingRedirect[j];

                                builder.AppendLine(bindingRedirect.OldVersion, $"{padding}[Dependency {i} Binding Redirect {j}] Old version");
                                builder.AppendLine(bindingRedirect.NewVersion, $"{padding}[Dependency {i} Binding Redirect {j}] New version");
                            }
                        }
                    }

                    if (dependency is not null)
                        builder.AppendLine(dependency.Optional, $"{padding}[Dependency {i}] Optional");
                }
            }

            if (assemblyManifest.File is not null && assemblyManifest.File.Length > 0)
            {
                for (int i = 0; i < assemblyManifest.File.Length; i++)
                {
                    var file = assemblyManifest.File[i];
                    if (file is null)
                    {
                        builder.AppendLine($"{padding}[File {i}] [NULL]");
                        continue;
                    }

                    builder.AppendLine(file.Name, $"{padding}[File {i}] Name");
                    builder.AppendLine(file.Hash, $"{padding}[File {i}] Hash");
                    builder.AppendLine(file.HashAlgorithm, $"{padding}[File {i}] Hash algorithm");
                    builder.AppendLine(file.Size, $"{padding}[File {i}] Size");

                    if (file.COMClass is not null && file.COMClass.Length > 0)
                    {
                        for (int j = 0; j < file.COMClass.Length; j++)
                        {
                            var comClass = file.COMClass[j];

                            builder.AppendLine(comClass.CLSID, $"{padding}[File {i} COM Class {j}] CLSID");
                            builder.AppendLine(comClass.ThreadingModel, $"{padding}[File {i} COM Class {j}] Threading model");
                            builder.AppendLine(comClass.ProgID, $"{padding}[File {i} COM Class {j}] Prog ID");
                            builder.AppendLine(comClass.TLBID, $"{padding}[File {i} COM Class {j}] TLBID");
                            builder.AppendLine(comClass.Description, $"{padding}[File {i} COM Class {j}] Description");

                            if (comClass.ProgIDs is not null && comClass.ProgIDs.Length > 0)
                            {
                                for (int k = 0; k < comClass.ProgIDs.Length; k++)
                                {
                                    var progId = comClass.ProgIDs[k];
                                    builder.AppendLine(progId.Value, $"{padding}[File {i} COM Class {j} Prog ID {k}] Value");
                                }
                            }
                        }
                    }

                    if (file.COMInterfaceProxyStub is not null && file.COMInterfaceProxyStub.Length > 0)
                    {
                        for (int j = 0; j < file.COMInterfaceProxyStub.Length; j++)
                        {
                            var comInterfaceProxyStub = file.COMInterfaceProxyStub[j];

                            builder.AppendLine(comInterfaceProxyStub.IID, $"{padding}[File {i} COM Interface Proxy Stub {j}] IID");
                            builder.AppendLine(comInterfaceProxyStub.Name, $"{padding}[File {i} COM Interface Proxy Stub {j}] Name");
                            builder.AppendLine(comInterfaceProxyStub.TLBID, $"{padding}[File {i} COM Interface Proxy Stub {j}] TLBID");
                            builder.AppendLine(comInterfaceProxyStub.NumMethods, $"{padding}[File {i} COM Interface Proxy Stub {j}] Number of methods");
                            builder.AppendLine(comInterfaceProxyStub.ProxyStubClsid32, $"{padding}[File {i} COM Interface Proxy Stub {j}] Proxy stub (CLSID32)");
                            builder.AppendLine(comInterfaceProxyStub.BaseInterface, $"{padding}[File {i} COM Interface Proxy Stub {j}] Base interface");
                        }
                    }

                    if (file.Typelib is not null && file.Typelib.Length > 0)
                    {
                        for (int j = 0; j < file.Typelib.Length; j++)
                        {
                            var typeLib = file.Typelib[j];

                            builder.AppendLine(typeLib.TLBID, $"{padding}[File {i} Type Lib {j}] TLBID");
                            builder.AppendLine(typeLib.Version, $"{padding}[File {i} Type Lib {j}] Version");
                            builder.AppendLine(typeLib.HelpDir, $"{padding}[File {i} Type Lib {j}] Help directory");
                            builder.AppendLine(typeLib.ResourceID, $"{padding}[File {i} Type Lib {j}] Resource ID");
                            builder.AppendLine(typeLib.Flags, $"{padding}[File {i} Type Lib {j}] Flags");
                        }
                    }

                    if (file.WindowClass is not null && file.WindowClass.Length > 0)
                    {
                        for (int j = 0; j < file.WindowClass.Length; j++)
                        {
                            var windowClass = file.WindowClass[j];

                            builder.AppendLine(windowClass.Versioned, $"{padding}[File {i} Window Class {j}] Versioned");
                            builder.AppendLine(windowClass.Value, $"{padding}[File {i} Window Class {j}] Value");
                        }
                    }
                }
            }

            if (assemblyManifest.EverythingElse is not null && assemblyManifest.EverythingElse.Length > 0)
            {
                for (int i = 0; i < assemblyManifest.EverythingElse.Length; i++)
                {
                    var thing = assemblyManifest.EverythingElse[i];
                    if (thing is XmlElement element)
                    {
                        builder.AppendLine(element.OuterXml, $"{padding}Unparsed XML Element {i}");
                    }
                    else
                    {
                        builder.AppendLine($"{padding}Unparsed Item {i}: {thing ?? "[NULL]"}");
                    }
                }
            }
        }

        private static void PrintResourceUNKNOWN(Data.Models.PortableExecutable.Resource.DataEntry entry, int level, object resourceType, StringBuilder builder)
        {
            string padding = new(' ', (level + 1) * 2);

            // Print the type first
            if (resourceType is uint numericType)
                builder.AppendLine($"{padding}Type {(ResourceType)numericType} found, not parsed yet");
            else if (resourceType is string stringType)
                builder.AppendLine($"{padding}Type {stringType} found, not parsed yet");
            else
                builder.AppendLine($"{padding}Unknown type {resourceType} found, not parsed yet");

            // Then print the data, if needed
            if (entry.Data is null)
            {
                builder.AppendLine($"{padding}Data: [NULL] (This may indicate a very large resource)");
            }
            else
            {
                int offset = 0;

                byte[] magic = entry.Data.ReadBytes(ref offset, Math.Min(entry.Data.Length, 16));
                if (magic.StartsWith([0x4D, 0x5A]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded Executable File]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x4D, 0x53, 0x46, 0x54]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded OLE Library File]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x50, 0x4B, 0x03, 0x04]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded PKZIP file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x50, 0x4B, 0x05, 0x06]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded empty PKZIP file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith([0x50, 0x4B, 0x07, 0x08]))
                {
                    builder.AppendLine($"{padding}Data: [Embedded spanned PKZIP file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith(Data.Models.RAR.Constants.OldSignatureBytes))
                {
                    builder.AppendLine($"{padding}Data: [Embedded RAR file]"); // TODO: Parse this out and print separately
                }
                else if (magic.StartsWith(Data.Models.RAR.Constants.NewSignatureBytes))
                {
                    builder.AppendLine($"{padding}Data: [Embedded RAR5 file]"); // TODO: Parse this out and print separately
                }
                else
                {
                    builder.AppendLine(magic, $"{padding}Data");

                    //if (entry.Data is not null)
                    //    builder.AppendLine(entry.Data, $"{padding}Value (Byte Data)");
                    //if (entry.Data is not null)
                    //    builder.AppendLine(Encoding.ASCII.GetString(entry.Data), $"{padding}Value (ASCII)");
                    //if (entry.Data is not null)
                    //    builder.AppendLine(Encoding.UTF8.GetString(entry.Data), $"{padding}Value (UTF-8)");
                    //if (entry.Data is not null)
                    //    builder.AppendLine(Encoding.Unicode.GetString(entry.Data), $"{padding}Value (Unicode)");
                }
            }
        }
    }
}
