const std = @import("../std.zig");
const sha2 = std.crypto.hash.sha2;
pub fn Composition(comptime H1: type, comptime H2: type) type {
    return struct {
        const Self = @This();
        H1: H1,
        H2: H2,
        
        pub const digest_length = H1.digest_length;
        
        pub const block_length = H1.block_length;
        
        pub const Options = struct {
            
            H1: H1.Options = .{},
            
            H2: H2.Options = .{},
        };
        
        pub fn init(options: Options) Self {
            return Self{ .H1 = H1.init(options.H1), .H2 = H2.init(options.H2) };
        }
        
        pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
            var d = Self.init(options);
            d.update(b);
            d.final(out);
        }
        
        pub fn update(d: *Self, b: []const u8) void {
            d.H2.update(b);
        }
        
        pub fn final(d: *Self, out: *[digest_length]u8) void {
            var H2_digest: [H2.digest_length]u8 = undefined;
            d.H2.final(&H2_digest);
            d.H1.update(&H2_digest);
            d.H1.final(out);
        }
    };
}
pub const Sha256oSha256 = Composition(sha2.Sha256, sha2.Sha256);
pub const Sha384oSha384 = Composition(sha2.Sha384, sha2.Sha384);
pub const Sha512oSha512 = Composition(sha2.Sha512, sha2.Sha512);
test "Hash composition" {
    const Sha256 = sha2.Sha256;
    const msg = "test";
    var out: [Sha256oSha256.digest_length]u8 = undefined;
    Sha256oSha256.hash(msg, &out, .{});
    var t: [Sha256.digest_length]u8 = undefined;
    Sha256.hash(msg, &t, .{});
    var out2: [Sha256.digest_length]u8 = undefined;
    Sha256.hash(&t, &out2, .{});
    try std.testing.expectEqualSlices(u8, &out, &out2);
}