using System.Text;
using GsaEditor.Core.Models;

namespace GsaEditor.Core.IO;

/// <summary>
/// Writes a <see cref="GsaArchive"/> to a binary .gsa stream or file.
/// Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
/// </summary>
public static class GsaWriter
{
    /// <summary>Magic word for the NARC (legacy) format.</summary>
    private const uint MAGIC_NARC = 0x4E415243;

    /// <summary>Magic word for the NARD (enhanced) format.</summary>
    private const uint MAGIC_NARD = 0x4E415244;

    /// <summary>
    /// End-of-archive terminator written where the next entry's alias length would be.
    /// The engine's sequential reader stops when it hits this invalid length.
    /// </summary>
    private const uint TERMINATOR = 0xFFFFFFFF;

    /// <summary>
    /// Writes the archive to the specified file path.
    /// </summary>
    /// <param name="archive">The archive to write.</param>
    /// <param name="filePath">The output file path.</param>
    public static void Write(GsaArchive archive, string filePath)
    {
        using var stream = File.Create(filePath);
        Write(archive, stream);
    }

    /// <summary>
    /// Writes the archive to the given stream.
    /// Each entry's <see cref="GsaEntry.DataOffset"/> is updated to reflect the new position in the output.
    /// </summary>
    /// <param name="archive">The archive to write.</param>
    /// <param name="stream">A writable stream.</param>
    public static void Write(GsaArchive archive, Stream stream)
    {
        using var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true);

        // Write global header
        if (archive.Format == GsaFormat.NARC)
        {
            writer.Write(MAGIC_NARC);
        }
        else
        {
            writer.Write(MAGIC_NARD);
            writer.Write(archive.OffsetPadding);
            writer.Write(archive.SizePadding);
        }

        // Write each entry sequentially
        foreach (var entry in archive.Entries)
        {
            WriteEntry(writer, entry, archive);
        }

        // Write the end-of-archive terminator, aligned like an entry header
        if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
            WritePadding(writer, archive.OffsetPadding);
        writer.Write(TERMINATOR);
    }

    /// <summary>
    /// Writes a single file entry at the current stream position.
    /// </summary>
    /// <param name="writer">The binary writer.</param>
    /// <param name="entry">The entry to write.</param>
    /// <param name="archive">The parent archive (used for NARD padding alignment).</param>
    private static void WriteEntry(BinaryWriter writer, GsaEntry entry, GsaArchive archive)
    {
        // NARD: align stream position before writing entry header
        if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
            WritePadding(writer, archive.OffsetPadding);

        // Write alias
        byte[] aliasBytes = Encoding.ASCII.GetBytes(entry.Alias);
        writer.Write((uint)aliasBytes.Length);
        writer.Write(aliasBytes);

        // Write compression flag
        writer.Write((byte)(entry.IsCompressed ? 1 : 0));

        // Write original (decompressed) length
        writer.Write(entry.OriginalLength);

        // Write compressed length (only if compressed)
        if (entry.IsCompressed)
        {
            writer.Write(entry.CompressedLength);
        }

        // NARD: align stream position before writing the data block
        if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
            WritePadding(writer, archive.OffsetPadding);

        // Update the entry's data offset to reflect the new position
        entry.DataOffset = writer.BaseStream.Position;

        // Write raw data
        writer.Write(entry.RawData);
    }

    /// <summary>
    /// Writes zero-bytes to pad the stream to the next aligned boundary.
    /// </summary>
    /// <param name="writer">The binary writer.</param>
    /// <param name="alignment">The alignment boundary in bytes.</param>
    private static void WritePadding(BinaryWriter writer, int alignment)
    {
        if (alignment <= 1) return;
        long pos = writer.BaseStream.Position;
        long remainder = pos % alignment;
        if (remainder != 0)
        {
            int paddingBytes = (int)(alignment - remainder);
            writer.Write(new byte[paddingBytes]);
        }
    }
}
