using System;
using System.Text;
using SabreTools.Data.Models.STFS;
using SabreTools.Text.Extensions;

namespace SabreTools.Wrappers
{
    public partial class STFS : 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("Secure Transacted File System Information:");
            builder.AppendLine("-------------------------");
            builder.AppendLine();

            Print(builder, Model.Header);

            // Do not print info about Hash Table Blocks or Data Blocks
        }

        protected static void Print(StringBuilder builder, Header header)
        {
            builder.AppendLine(header.MagicBytes, "  Magic Bytes");

            builder.AppendLine();

            Print(builder, header.Signature);

            Print(builder, header.LicensingData);

            builder.AppendLine(header.HeaderHash, "  Header Hash");
            builder.AppendLine(header.HeaderSize, "  Header Size");
            builder.AppendLine(header.ContentType, "  Content Type"); // See Enums.ContentType
            builder.AppendLine(header.MetadataVersion, "  Metadata Version");
            builder.AppendLine(header.ContentSize, "  Content Size");
            builder.AppendLine(header.MediaID, "  Media ID");
            builder.AppendLine(header.Version, "  Version");
            builder.AppendLine(header.BaseVersion, "  Base Version");
            builder.AppendLine(header.TitleID, "  Title ID");
            builder.AppendLine(header.Platform, "  Platform");
            builder.AppendLine(header.ExecutableType, "  Executable Type");
            builder.AppendLine(header.DiscNumber, "  Disc Number");
            builder.AppendLine(header.DiscInSet, "  Disc in Set");
            builder.AppendLine(header.SaveGameID, "  Save Game ID");
            builder.AppendLine(header.ConsoleID, "  Console ID");
            builder.AppendLine(header.ProfileID, "  Profile ID");

            builder.AppendLine();

            Print(builder, header.VolumeDescriptor);

            builder.AppendLine(header.DataFileCount, "  Data File Count");
            builder.AppendLine(header.DataFileCombinedSize, "  Data File Combined Size");
            builder.AppendLine(header.DescriptorType, "  Descriptor Type");
            builder.AppendLine(header.Reserved, "  Reserved");

            if (header.MetadataVersion == 2)
            {
                builder.AppendLine(header.SeriesID, "  Series ID");
                builder.AppendLine(header.SeasonID, "  Season ID");
                builder.AppendLine(header.SeasonNumber, "  Season Number");
                builder.AppendLine(header.EpisodeNumber, "  Episode Number");
            }

            if (Array.TrueForAll(header.Padding, b => b == 0))
                builder.AppendLine("Zeroed", "  Padding");
            else
                builder.AppendLine(header.Padding, "  Padding");

            builder.AppendLine(header.DeviceID, "  Device ID");

            if (Array.TrueForAll(header.DisplayName, b => b == 0))
            {
                builder.AppendLine("Zeroed", "  Display Name");
            }
            else
            {
                for (int i = 0; i < 18; i++)
                {
                    byte[] localeString = new byte[128];
                    Array.Copy(header.DisplayName, i * 128, localeString, 0, 128);
                    if (!Array.TrueForAll(localeString, b => b == 0))
                    {
                        builder.AppendLine(localeString, $"  Display Name {i}");
                        builder.AppendLine(Encoding.BigEndianUnicode.GetString(localeString), $"  Display Name {i} (Parsed)");
                    }
                }
            }

            if (Array.TrueForAll(header.DisplayDescription, b => b == 0))
            {
                builder.AppendLine("Zeroed", "  Display Description");
            }
            else
            {
                for (int i = 0; i < 18; i++)
                {
                    byte[] localeString = new byte[128];
                    Array.Copy(header.DisplayDescription, i * 128, localeString, 0, 128);
                    if (!Array.TrueForAll(localeString, b => b == 0))
                    {
                        builder.AppendLine(localeString, $"  Display Description {i}");
                        builder.AppendLine(Encoding.BigEndianUnicode.GetString(localeString), $"  Display Description {i} (Parsed)");
                    }
                }
            }

            if (Array.TrueForAll(header.PublisherName, b => b == 0))
            {
                builder.AppendLine("Zeroed", "  Publisher Name");
            }
            else
            {
                builder.AppendLine(header.PublisherName, "  Publisher Name");
                builder.AppendLine(Encoding.BigEndianUnicode.GetString(header.PublisherName), "  Publisher Name (Parsed)");
            }

            if (Array.TrueForAll(header.TitleName, b => b == 0))
            {
                builder.AppendLine("Zeroed", "  Title Name");
            }
            else
            {
                builder.AppendLine(header.TitleName, "  Title Name");
                builder.AppendLine(Encoding.BigEndianUnicode.GetString(header.TitleName), "  Title Name (Parsed)");
            }

            builder.AppendLine(header.TransferFlags, "  Transfer Flags"); // See Enums.TransferFlags
            builder.AppendLine(header.ThumbnailImageSize, "  Thumbnail Image Size");
            builder.AppendLine(header.TitleThumbnailImageSize, "  Title Thumbnail Image Size");

            if (header.AdditionalDisplayNames is not null)
            {
                for (int i = 0; i < 6; i++)
                {
                    byte[] localeString = new byte[128];
                    Array.Copy(header.AdditionalDisplayNames, i * 128, localeString, 0, 128);
                    if (!Array.TrueForAll(localeString, b => b == 0))
                    {
                        builder.AppendLine(localeString, $"  Additional Display Name {i}");
                        builder.AppendLine(Encoding.BigEndianUnicode.GetString(localeString), $"  Additional Display Name {i} (Parsed)");
                    }
                }
            }

            if (header.AdditionalDisplayDescriptions is not null)
            {
                for (int i = 0; i < 6; i++)
                {
                    byte[] localeString = new byte[128];
                    Array.Copy(header.AdditionalDisplayDescriptions, i * 128, localeString, 0, 128);
                    if (!Array.TrueForAll(localeString, b => b == 0))
                    {
                        builder.AppendLine(localeString, $"  Additional Display Description {i}");
                        builder.AppendLine(Encoding.BigEndianUnicode.GetString(localeString), $"  Additional Display Description {i} (Parsed)");
                    }
                }
            }

            if (header.InstallerHeader is not null)
                Print(builder, header.InstallerHeader);

            builder.AppendLine();
        }

        protected static void Print(StringBuilder builder, Signature? signature)
        {
            builder.AppendLine("  Signature Information");
            builder.AppendLine("  -------------------------");

            if (signature is MicrosoftSignature ms)
            {
                builder.AppendLine(ms.PackageSignature, "    Package Signature");
                if (Array.TrueForAll(ms.Padding, b => b == 0))
                    builder.AppendLine("Zeroed", "    Padding");
                else
                    builder.AppendLine(ms.Padding, "    Padding");
            }
            else if (signature is ConsoleSignature cs)
            {
                builder.AppendLine(cs.CertificateSize, "    Certificate Size");
                builder.AppendLine(cs.ConsoleID, "    Console ID");
                builder.AppendLine(cs.PartNumber, "    Part Number");
                builder.AppendLine(cs.ConsoleType, "    Console Type");
                builder.AppendLine(cs.CertificateDate, "    Certificate Date");
                builder.AppendLine(cs.PublicExponent, "    Public Exponent");
                builder.AppendLine(cs.PublicModulus, "    Public Modulus");
                builder.AppendLine(cs.CertificateSignature, "    Certificate Signature");
                builder.AppendLine(cs.Signature, "    Signature");
            }
            else
            {
                builder.AppendLine("    Unknown Signature Type");
            }

            builder.AppendLine();
        }

        protected static void Print(StringBuilder builder, LicenseEntry[] ld)
        {
            builder.AppendLine("  Licensing Data Information");
            builder.AppendLine("  -------------------------");

            int lastLicenseData = 0;
            for (int i = ld.Length - 1; i >= 0; i--)
            {
                if (ld[i].LicenseID != 0 || ld[i].LicenseBits != 0 || ld[i].LicenseFlags != 0)
                {
                    lastLicenseData = i + 1;
                    break;
                }

                if (i == 0)
                    builder.AppendLine("Zeroed", "    Licensing Data");
            }

            for (int i = 0; i < lastLicenseData; i++)
            {
                if (ld[i].LicenseID == 0 && ld[i].LicenseBits == 0 && ld[i].LicenseFlags == 0)
                {
                    builder.AppendLine("Zeroed", $"    License Entry {i}");
                }
                else
                {
                    builder.AppendLine(ld[i].LicenseID, $"    License Entry {i} ID");
                    builder.AppendLine(ld[i].LicenseBits, $"    License Entry {i} Bits");
                    builder.AppendLine(ld[i].LicenseFlags, $"    License Entry {i} Flags");
                }
            }

            builder.AppendLine();
        }

        protected static void Print(StringBuilder builder, VolumeDescriptor? vd)
        {
            builder.AppendLine("  Volume Descriptor Information");
            builder.AppendLine("  -------------------------");
            if (vd is STFSDescriptor stfs)
            {
                builder.AppendLine(stfs.VolumeDescriptorSize, "    Volume Descriptor Size");
                builder.AppendLine(stfs.Reserved, "    Reserved");
                builder.AppendLine(stfs.BlockSeparation, "    Block Separation");
                builder.AppendLine(stfs.FileTableBlockCount, "    File Table Block Count");
                builder.AppendLine((uint)stfs.FileTableBlockNumber, "    File Table Block Number");
                builder.AppendLine(stfs.TopHashTableHash, "    Top Hash Table Hash");
                builder.AppendLine(stfs.TotalAllocatedBlockCount, "    Total Allocated Block Count");
                builder.AppendLine(stfs.TotalUnallocatedBlockCount, "    Total Unallocated Block Count");
            }
            else if (vd is SVODDescriptor svod)
            {
                builder.AppendLine(svod.VolumeDescriptorSize, "    Volume Descriptor Size");
                builder.AppendLine(svod.BlockCacheElementCount, "    Block Cache Element Count");
                builder.AppendLine(svod.WorkerThreadProcessor, "    Worker Thread Processor");
                builder.AppendLine(svod.WorkerThreadPriority, "    Worker Thread Priority");
                builder.AppendLine(svod.Hash, "    Hash");
                builder.AppendLine(svod.DeviceFeatures, "    Device Features");
                builder.AppendLine((uint)svod.DataBlockCount, "    Data Block Count");
                builder.AppendLine((uint)svod.DataBlockOffset, "    Data Block Offset");
                builder.AppendLine(svod.Padding, "    Padding");
            }
            else
            {
                builder.AppendLine("    Unknown Volume Descriptor Type");
            }

            builder.AppendLine();
        }

        protected static void Print(StringBuilder builder, InstallerHeader installerHeader)
        {
            builder.AppendLine("  Installer Information");
            builder.AppendLine("  -------------------------");

            builder.AppendLine(installerHeader.InstallerType, "    Installer Type");
            builder.AppendLine(Encoding.UTF8.GetString(installerHeader.InstallerType), "    Installer Type (Parsed)");

            if (installerHeader is InstallerUpdateHeader updateHeader)
            {
                builder.AppendLine(updateHeader.InstallerBaseVersion, "    Installer Base Version");

                uint bvMajor = updateHeader.InstallerBaseVersion >> 28; // Top 4 bits
                uint bvMinor = (updateHeader.InstallerBaseVersion >> 24) & 0xF; // Next top 4 bits
                uint bvBuild = (updateHeader.InstallerBaseVersion >> 8) & 0xFFFF; // Next 16 bits
                uint bvRevision = updateHeader.InstallerBaseVersion & 0xFF; // Lowest 8 bits
                builder.AppendLine($"{bvMajor}.{bvMinor}.{bvBuild}.{bvRevision}", "    Installer Base Version (Parsed)");

                builder.AppendLine(updateHeader.InstallerVersion, "    Installer Version");

                uint vMajor = updateHeader.InstallerVersion >> 28; // Top 4 bits
                uint vMinor = (updateHeader.InstallerVersion >> 24) & 0xF; // Next top 4 bits
                uint vBuild = (updateHeader.InstallerVersion >> 8) & 0xFFFF; // Next 8 bits
                uint vRevision = updateHeader.InstallerVersion & 0xFF; // Lowest 8 bits
                builder.AppendLine($"{vMajor}.{vMinor}.{vBuild}.{vRevision}", "    Installer Version (Parsed)");
            }
            else if (installerHeader is InstallerCacheHeader cacheHeader)
            {
                builder.AppendLine(cacheHeader.ResumeState, "    Resume State"); // See Enums.ResumeState
                builder.AppendLine(cacheHeader.CurrentFileIndex, "    Current File Index");
                builder.AppendLine(cacheHeader.BytesProcessed, "    Bytes Processed");
                builder.AppendLine(cacheHeader.LastModifiedDateTime, "    Last Modified Date Time");

                DateTime datetime = DateTime.FromFileTime(cacheHeader.LastModifiedDateTime);
                builder.AppendLine(datetime.ToString("yyyy-MM-dd HH:mm:ss"), "    Last Modified Date Time (Parsed)");

                builder.AppendLine(cacheHeader.ResumeData, "    Resume Data");
            }

            builder.AppendLine();
        }
    }
}
