using System.IO.Compression;

namespace GsaEditor.Core.Compression;

/// <summary>
/// Provides zlib compression and decompression utilities for GSA archive entries.
/// The GSA format stores zlib-wrapped data: 2-byte header (CMF+FLG) + deflate stream + 4-byte Adler-32 checksum.
/// This helper skips the 2-byte zlib header and uses <see cref="DeflateStream"/> for the core deflate data.
/// </summary>
public static class ZlibHelper
{
    /// <summary>
    /// Decompresses zlib-wrapped data into a byte array of the specified original size.
    /// Skips the 2-byte zlib header before feeding data to <see cref="DeflateStream"/>.
    /// </summary>
    /// <param name="compressedData">The zlib-wrapped compressed data (header + deflate + checksum).</param>
    /// <param name="originalLength">The expected decompressed size in bytes.</param>
    /// <returns>The decompressed byte array.</returns>
    /// <exception cref="InvalidDataException">Thrown when decompression fails or data is corrupt.</exception>
    public static byte[] Decompress(byte[] compressedData, uint originalLength)
    {
        if (compressedData.Length < 2)
            throw new InvalidDataException("Compressed data is too short to contain a zlib header.");

        // Skip the 2-byte zlib header (typically 0x78 0x9C for default compression)
        using var ms = new MemoryStream(compressedData, 2, compressedData.Length - 2);
        using var deflate = new DeflateStream(ms, CompressionMode.Decompress);

        var result = new byte[originalLength];
        int totalRead = 0;
        while (totalRead < result.Length)
        {
            int bytesRead = deflate.Read(result, totalRead, result.Length - totalRead);
            if (bytesRead == 0)
                break;
            totalRead += bytesRead;
        }

        return result;
    }

    /// <summary>
    /// Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
    /// </summary>
    /// <param name="data">The uncompressed data to compress.</param>
    /// <returns>The zlib-wrapped compressed byte array.</returns>
    public static byte[] Compress(byte[] data)
    {
        using var output = new MemoryStream();

        // Write zlib header: CMF=0x78 (deflate method, 32K window), FLG=0x9C (default level, valid check bits)
        output.WriteByte(0x78);
        output.WriteByte(0x9C);

        using (var deflate = new DeflateStream(output, CompressionLevel.Optimal, leaveOpen: true))
        {
            deflate.Write(data, 0, data.Length);
        }

        // Write Adler-32 checksum in big-endian byte order
        uint adler = ComputeAdler32(data);
        output.WriteByte((byte)(adler >> 24));
        output.WriteByte((byte)(adler >> 16));
        output.WriteByte((byte)(adler >> 8));
        output.WriteByte((byte)adler);

        return output.ToArray();
    }

    /// <summary>
    /// Computes the Adler-32 checksum of the given data.
    /// </summary>
    /// <param name="data">The data to checksum.</param>
    /// <returns>The 32-bit Adler-32 checksum value.</returns>
    private static uint ComputeAdler32(byte[] data)
    {
        const uint MOD_ADLER = 65521;
        uint a = 1, b = 0;

        for (int i = 0; i < data.Length; i++)
        {
            a = (a + data[i]) % MOD_ADLER;
            b = (b + a) % MOD_ADLER;
        }

        return (b << 16) | a;
    }
}
