using System.Collections.Generic;
using System.Text;
using SabreTools.Data.Extensions;
using SabreTools.Data.Models.NewExecutable;
using SabreTools.Text.Extensions;

namespace SabreTools.Wrappers
{
    public partial class NewExecutable : 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("New Executable Information:");
            builder.AppendLine("-------------------------");
            builder.AppendLine();

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

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

            // Tables
            Print(builder, Model.SegmentTable);
            Print(builder, Model.ResourceTable);
            Print(builder, Model.ResidentNameTable);
            Print(builder, Model.ModuleReferenceTable, Model.Stub.Header, Model.Header);
            Print(builder, Model.ImportedNameTable);
            Print(builder, Model.EntryTable);
            Print(builder, Model.NonResidentNameTable);
        }

        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, ExecutableHeader header)
        {
            builder.AppendLine("  Header Information:");
            builder.AppendLine("  -------------------------");
            builder.AppendLine(header.Magic, "  Magic number");
            builder.AppendLine(header.LinkerVersion, "  Linker version");
            builder.AppendLine(header.LinkerRevision, "  Linker revision");
            builder.AppendLine(header.EntryTableOffset, "  Entry table offset");
            builder.AppendLine(header.EntryTableSize, "  Entry table size");
            builder.AppendLine(header.CrcChecksum, "  CRC checksum");
            builder.AppendLine($"  Flag word: {header.FlagWord} (0x{header.FlagWord:X})");
            builder.AppendLine(header.AutomaticDataSegmentNumber, "  Automatic data segment number");
            builder.AppendLine(header.InitialHeapAlloc, "  Initial heap allocation");
            builder.AppendLine(header.InitialStackAlloc, "  Initial stack allocation");
            builder.AppendLine(header.InitialCSIPSetting, "  Initial CS:IP setting");
            builder.AppendLine(header.InitialSSSPSetting, "  Initial SS:SP setting");
            builder.AppendLine(header.FileSegmentCount, "  File segment count");
            builder.AppendLine(header.ModuleReferenceTableSize, "  Module reference table size");
            builder.AppendLine(header.NonResidentNameTableSize, "  Non-resident name table size");
            builder.AppendLine(header.SegmentTableOffset, "  Segment table offset");
            builder.AppendLine(header.ResourceTableOffset, "  Resource table offset");
            builder.AppendLine(header.ResidentNameTableOffset, "  Resident name table offset");
            builder.AppendLine(header.ModuleReferenceTableOffset, "  Module reference table offset");
            builder.AppendLine(header.ImportedNamesTableOffset, "  Imported names table offset");
            builder.AppendLine(header.NonResidentNamesTableOffset, "  Non-resident name table offset");
            builder.AppendLine(header.MovableEntriesCount, "  Moveable entries count");
            builder.AppendLine(header.SegmentAlignmentShiftCount, "  Segment alignment shift count");
            builder.AppendLine(header.ResourceEntriesCount, "  Resource entries count");
            builder.AppendLine($"  Target operating system: {header.TargetOperatingSystem} (0x{header.TargetOperatingSystem:X})");
            builder.AppendLine($"  Additional flags: {header.AdditionalFlags} (0x{header.AdditionalFlags:X})");
            builder.AppendLine(header.ReturnThunkOffset, "  Return thunk offset");
            builder.AppendLine(header.SegmentReferenceThunkOffset, "  Segment reference thunk offset");
            builder.AppendLine(header.MinCodeSwapAreaSize, "  Minimum code swap area size");
            builder.AppendLine(header.WindowsSDKRevision, "  Windows SDK revision");
            builder.AppendLine(header.WindowsSDKVersion, "  Windows SDK version");
            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, SegmentTableEntry[] entries)
        {
            builder.AppendLine("  Segment Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries.Length == 0)
            {
                builder.AppendLine("  No segment table items");
                builder.AppendLine();
                return;
            }

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

                builder.AppendLine($"  Segment Table Entry {i}");
                builder.AppendLine(entry.Offset, "    Offset");
                builder.AppendLine(entry.Length, "    Length");
                builder.AppendLine($"    Flag word: {entry.FlagWord} (0x{entry.FlagWord:X})");
                builder.AppendLine(entry.MinimumAllocationSize, "    Minimum allocation size");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, ResourceTable table)
        {
            builder.AppendLine("  Resource Table Information:");
            builder.AppendLine("  -------------------------");
            builder.AppendLine(table.AlignmentShiftCount, "  Alignment shift count");
            if (table.ResourceTypes.Length == 0)
            {
                builder.AppendLine("  No resource table items");
            }
            else
            {
                for (int i = 0; i < table.ResourceTypes.Length; i++)
                {
                    // TODO: If not integer type, print out name
                    var entry = table.ResourceTypes[i];

                    builder.AppendLine($"  Resource Table Entry {i}");
                    builder.AppendLine(entry.TypeID, "    Type ID");
                    builder.AppendLine(entry.ResourceCount, "    Resource count");
                    builder.AppendLine(entry.Reserved, "    Reserved");
                    builder.AppendLine("    Resources = ");
                    if (entry.ResourceCount == 0 || entry.Resources.Length == 0)
                    {
                        builder.AppendLine("      No resource items");
                    }
                    else
                    {
                        for (int j = 0; j < entry.Resources.Length; j++)
                        {
                            // TODO: If not integer type, print out name
                            var resource = entry.Resources[j];
                            builder.AppendLine($"      Resource Entry {j}");
                            if (resource is null)
                            {
                                builder.AppendLine("      [NULL]");
                                continue;
                            }

                            builder.AppendLine(resource.Offset, "        Offset");
                            builder.AppendLine(resource.Length, "        Length");
                            builder.AppendLine($"        Flag word: {resource.FlagWord} (0x{resource.FlagWord:X})");
                            builder.AppendLine(resource.ResourceID, "        Resource ID");
                            builder.AppendLine(resource.Reserved, "        Reserved");
                        }
                    }
                }
            }

            if (table.TypeAndNameStrings.Count == 0)
            {
                builder.AppendLine("  No resource table type/name strings");
            }
            else
            {
                foreach (var typeAndNameString in table.TypeAndNameStrings)
                {
                    builder.AppendLine($"  Resource Type/Name Offset {typeAndNameString.Key}");
                    if (typeAndNameString.Value is null)
                    {
                        builder.AppendLine("    [NULL]");
                        continue;
                    }

                    builder.AppendLine(typeAndNameString.Value.Length, "    Length");
                    builder.AppendLine(typeAndNameString.Value.Text, "    Text", Encoding.ASCII);
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, ResidentNameTableEntry[] entries)
        {
            builder.AppendLine("  Resident-Name Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries.Length == 0)
            {
                builder.AppendLine("  No resident-name table items");
                builder.AppendLine();
                return;
            }

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

                builder.AppendLine($"  Resident-Name Table Entry {i}");
                builder.AppendLine(entry.Length, "    Length");
                builder.AppendLine(entry.NameString, "    Name string", Encoding.ASCII);
                builder.AppendLine(entry.OrdinalNumber, "    Ordinal number");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, ModuleReferenceTableEntry[] entries, Data.Models.MSDOS.ExecutableHeader stub, ExecutableHeader header)
        {
            builder.AppendLine("  Module-Reference Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries.Length == 0)
            {
                builder.AppendLine("  No module-reference table items");
                builder.AppendLine();
                return;
            }

            for (int i = 0; i < entries.Length; i++)
            {
                // TODO: Read the imported names table and print value here
                var entry = entries[i];

                builder.AppendLine($"  Module-Reference Table Entry {i}");
                builder.AppendLine($"    Offset: {entry.Offset} (adjusted to be {entry.Offset + stub.NewExeHeaderAddr + header.ImportedNamesTableOffset})");
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, Dictionary<ushort, ImportedNameTableEntry> entries)
        {
            builder.AppendLine("  Imported-Name Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries.Count == 0)
            {
                builder.AppendLine("  No imported-name table items");
                builder.AppendLine();
                return;
            }

            foreach (var entry in entries)
            {
                builder.AppendLine($"  Imported-Name Table at Offset {entry.Key}");
                builder.AppendLine(entry.Value.Length, "    Length");
                builder.AppendLine(entry.Value.NameString, "    Name string", Encoding.ASCII);
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, EntryTableBundle[] entries)
        {
            builder.AppendLine("  Entry Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries.Length == 0)
            {
                builder.AppendLine("  No entry table items");
                builder.AppendLine();
                return;
            }

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

                builder.AppendLine($"  Entry Table Entry {i}");
                builder.AppendLine(entry.EntryCount, "    Entry count");
                builder.AppendLine(entry.SegmentIndicator, "    Segment indicator");
                switch (entry.GetEntryType())
                {
                    case SegmentEntryType.FixedSegment:
                        builder.AppendLine($"    Flag word: {entry.FixedFlagWord} (0x{entry.FixedFlagWord:X})");
                        builder.AppendLine(entry.FixedOffset, "    Offset");
                        break;
                    case SegmentEntryType.MoveableSegment:
                        builder.AppendLine($"    Flag word: {entry.MoveableFlagWord} (0x{entry.MoveableFlagWord:X})");
                        builder.AppendLine(entry.MoveableReserved, "    Reserved");
                        builder.AppendLine(entry.MoveableSegmentNumber, "    Segment number");
                        builder.AppendLine(entry.MoveableOffset, "    Offset");
                        break;
                    case SegmentEntryType.Unused:
                    default:
                        break;
                }
            }

            builder.AppendLine();
        }

        private static void Print(StringBuilder builder, NonResidentNameTableEntry[] entries)
        {
            builder.AppendLine("  Nonresident-Name Table Information:");
            builder.AppendLine("  -------------------------");
            if (entries.Length == 0)
            {
                builder.AppendLine("  No nonresident-name table items");
                builder.AppendLine();
                return;
            }

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

                builder.AppendLine($"  Nonresident-Name Table Entry {i}");
                builder.AppendLine(entry.Length, "    Length");
                builder.AppendLine(entry.NameString, "    Name string", Encoding.ASCII);
                builder.AppendLine(entry.OrdinalNumber, "    Ordinal number");
            }

            builder.AppendLine();
        }
    }
}
