//! The SCSI command descriptor blocks a transparent-SCSI (subclass 0x06) mass
//! storage device understands, and the parsers for what they return. Pure data —
//! host-testable. These CDBs go inside a Bulk-Only-Transport CBW (see
//! bulk-only-transport.zig).
//!
//! Every multi-byte SCSI field is **big-endian** — the opposite of the USB wire
//! ABI — so the LBA and transfer-length encodings are the load-bearing detail.

const std = @import("std");

// SCSI operation codes.
const op_test_unit_ready: u8 = 0x00;
const op_request_sense: u8 = 0x03;
const op_inquiry: u8 = 0x12;
const op_read_capacity_10: u8 = 0x25;
const op_read_10: u8 = 0x28;
const op_write_10: u8 = 0x2A;
const op_synchronize_cache_10: u8 = 0x35;

/// INQUIRY: standard device data (36 bytes: peripheral type, removable, vendor
/// and product strings).
pub fn inquiry(allocation_length: u8) [6]u8 {
    return .{ op_inquiry, 0, 0, 0, allocation_length, 0 };
}

/// TEST UNIT READY: no data; success (CSW passed) means the unit is ready.
pub fn testUnitReady() [6]u8 {
    return .{ op_test_unit_ready, 0, 0, 0, 0, 0 };
}

/// REQUEST SENSE: 18 bytes of sense data (sense key + ASC/ASCQ) explaining the
/// previous failure.
pub fn requestSense(allocation_length: u8) [6]u8 {
    return .{ op_request_sense, 0, 0, 0, allocation_length, 0 };
}

/// READ CAPACITY(10): 8 bytes back — the last LBA and the block size, both u32
/// big-endian. Block count is last_lba + 1.
pub fn readCapacity10() [10]u8 {
    return .{ op_read_capacity_10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
}

/// READ(10): read `blocks` logical blocks starting at `lba` into the data stage.
pub fn read10(lba: u32, blocks: u16) [10]u8 {
    var cdb = [_]u8{0} ** 10;
    cdb[0] = op_read_10;
    std.mem.writeInt(u32, cdb[2..6], lba, .big);
    std.mem.writeInt(u16, cdb[7..9], blocks, .big);
    return cdb;
}

/// WRITE(10): write `blocks` logical blocks starting at `lba` from the data stage.
pub fn write10(lba: u32, blocks: u16) [10]u8 {
    var cdb = [_]u8{0} ** 10;
    cdb[0] = op_write_10;
    std.mem.writeInt(u32, cdb[2..6], lba, .big);
    std.mem.writeInt(u16, cdb[7..9], blocks, .big);
    return cdb;
}

/// SYNCHRONIZE CACHE(10): commit the device's write cache to stable media. LBA 0
/// and block count 0 mean "the whole medium". No data stage. Without this a write
/// can sit in the USB flash controller's cache and be lost if power is cut right
/// after — which is exactly what a shutdown-time log flush hits on real hardware.
pub fn synchronizeCache10() [10]u8 {
    var cdb = [_]u8{0} ** 10;
    cdb[0] = op_synchronize_cache_10;
    return cdb;
}

/// Decode an 8-byte READ CAPACITY(10) reply.
pub fn parseCapacity(bytes: [8]u8) struct { last_lba: u32, block_size: u32 } {
    return .{
        .last_lba = std.mem.readInt(u32, bytes[0..4], .big),
        .block_size = std.mem.readInt(u32, bytes[4..8], .big),
    };
}

test "read/write CDBs encode the LBA and length big-endian" {
    const read = read10(0x01020304, 8);
    try std.testing.expectEqualSlices(u8, &.{ 0x28, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x08, 0x00 }, &read);

    const write = write10(0xAABBCCDD, 1);
    try std.testing.expectEqualSlices(u8, &.{ 0x2A, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x00, 0x00, 0x01, 0x00 }, &write);

    try std.testing.expectEqual(@as(u8, 0x25), readCapacity10()[0]);
    try std.testing.expectEqual(@as(u8, 0x12), inquiry(36)[0]);
    try std.testing.expectEqual(@as(u8, 36), inquiry(36)[4]);
    try std.testing.expectEqual(@as(u8, 0x00), testUnitReady()[0]);
}

test "read capacity parses last LBA and block size" {
    // last_lba = 0x0003FFFF (262144 blocks), block_size = 512.
    const capacity = parseCapacity(.{ 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0x00 });
    try std.testing.expectEqual(@as(u32, 0x0003FFFF), capacity.last_lba);
    try std.testing.expectEqual(@as(u32, 512), capacity.block_size);
}
