// SPEC-020: Capability Space (CSpace) Implementation
// Component: core/security/cspace
// Target: Ground Zero - Phase 1

const std = @import("std");

/// CapType: Closed enumeration of capability types (SPEC-020)
pub const CapType = enum(u8) {
    Null = 0,
    Entity = 1, // Control over Process/Fiber
    Channel = 2, // Access to ION Ring
    Memory = 3, // Access to Physical Frame
    Interrupt = 4, // IRQ mask/unmask control
    Time = 5, // Clock/Timer access
    Entropy = 6, // HWRNG access
};

/// Permission flags (SPEC-020)
pub const CapPerms = packed struct(u8) {
    read: bool = false,
    write: bool = false,
    execute: bool = false,
    map: bool = false,
    delegate: bool = false,
    revoke: bool = false,
    copy: bool = false,
    spawn: bool = false,
};

/// Capability structure (32 bytes, cache-line aligned)
pub const Capability = packed struct {
    cap_type: CapType, // 1 byte
    perms: CapPerms, // 1 byte
    _reserved: u16, // 2 bytes (alignment)
    object_id: u64, // 8 bytes (SipHash of resource)
    bounds_start: u64, // 8 bytes
    bounds_end: u64, // 8 bytes

    comptime {
        if (@sizeOf(Capability) != 32) {
            @compileError("Capability must be exactly 32 bytes");
        }
    }

    /// Create a null capability
    pub fn null_cap() Capability {
        return .{
            .cap_type = .Null,
            .perms = .{},
            ._reserved = 0,
            .object_id = 0,
            .bounds_start = 0,
            .bounds_end = 0,
        };
    }

    /// Check if capability is null
    pub fn is_null(self: *const Capability) bool {
        return self.cap_type == .Null;
    }

    /// Validate bounds
    pub fn check_bounds(self: *const Capability, addr: u64) bool {
        if (self.is_null()) return false;
        return addr >= self.bounds_start and addr < self.bounds_end;
    }

    /// Check permission
    pub fn has_perm(self: *const Capability, perm: CapPerms) bool {
        const self_bits = @as(u8, @bitCast(self.perms));
        const perm_bits = @as(u8, @bitCast(perm));
        return (self_bits & perm_bits) == perm_bits;
    }
};

/// CSpace: Per-fiber capability table
pub const CSPACE_SIZE = 64; // Maximum capabilities per fiber

pub const CSpace = struct {
    slots: [CSPACE_SIZE]Capability,
    epoch: u32, // For revocation
    fiber_id: u64, // Owner fiber
    _padding: u32, // Alignment

    /// Initialize empty CSpace
    pub fn init(fiber_id: u64) CSpace {
        var cs = CSpace{
            .slots = undefined,
            .epoch = 0,
            .fiber_id = fiber_id,
            ._padding = 0,
        };

        // Initialize all slots to Null
        for (&cs.slots) |*slot| {
            slot.* = Capability.null_cap();
        }

        return cs;
    }

    /// Find first empty slot
    pub fn find_empty_slot(self: *CSpace) ?usize {
        for (&self.slots, 0..) |*cap, i| {
            if (cap.is_null()) return i;
        }
        return null;
    }

    /// Grant capability (insert into CSpace)
    pub fn grant(self: *CSpace, cap: Capability) !usize {
        const slot = self.find_empty_slot() orelse return error.CSpaceFull;
        self.slots[slot] = cap;
        return slot;
    }

    /// Lookup capability by slot index
    pub fn lookup(self: *const CSpace, slot: usize) ?*const Capability {
        if (slot >= CSPACE_SIZE) return null;
        const cap = &self.slots[slot];
        if (cap.is_null()) return null;
        return cap;
    }

    /// Revoke capability (set to Null)
    pub fn revoke(self: *CSpace, slot: usize) void {
        if (slot >= CSPACE_SIZE) return;
        self.slots[slot] = Capability.null_cap();
    }

    /// Revoke all capabilities (epoch-based)
    pub fn revoke_all(self: *CSpace) void {
        for (&self.slots) |*cap| {
            cap.* = Capability.null_cap();
        }
        self.epoch +%= 1;
    }

    /// Delegate capability (Move or Copy)
    pub fn delegate(
        self: *CSpace,
        slot: usize,
        target: *CSpace,
        move: bool,
    ) !usize {
        const cap = self.lookup(slot) orelse return error.InvalidCapability;

        // Check DELEGATE permission
        if (!cap.has_perm(.{ .delegate = true })) {
            return error.NotDelegatable;
        }

        // Grant to target
        const new_slot = try target.grant(cap.*);

        // If move (not copy), revoke from source
        if (move or !cap.has_perm(.{ .copy = true })) {
            self.revoke(slot);
        }

        return new_slot;
    }
};

/// Global CSpace table (one per fiber)
/// This will be integrated with Fiber Control Block in kernel.nim
pub const MAX_FIBERS = 16;
var global_cspaces: [MAX_FIBERS]CSpace = undefined;
var cspaces_initialized: bool = false;

/// Initialize global CSpace table
pub export fn cspace_init() void {
    if (cspaces_initialized) return;

    for (&global_cspaces, 0..) |*cs, i| {
        cs.* = CSpace.init(i);
    }

    cspaces_initialized = true;
}

/// Get CSpace for fiber
pub export fn cspace_get(fiber_id: u64) ?*CSpace {
    if (!cspaces_initialized) return null;
    if (fiber_id >= MAX_FIBERS) return null;
    return &global_cspaces[fiber_id];
}

/// Grant capability to fiber (C ABI)
pub export fn cspace_grant_cap(
    fiber_id: u64,
    cap_type: u8,
    perms: u8,
    object_id: u64,
    bounds_start: u64,
    bounds_end: u64,
) i32 {
    const cs = cspace_get(fiber_id) orelse return -1;

    const cap = Capability{
        .cap_type = @enumFromInt(cap_type),
        .perms = @bitCast(perms),
        ._reserved = 0,
        .object_id = object_id,
        .bounds_start = bounds_start,
        .bounds_end = bounds_end,
    };

    const slot = cs.grant(cap) catch return -1;
    return @intCast(slot);
}

/// Lookup capability (C ABI)
pub export fn cspace_lookup(fiber_id: u64, slot: usize) ?*const Capability {
    const cs = cspace_get(fiber_id) orelse return null;
    return cs.lookup(slot);
}

/// Revoke capability (C ABI)
pub export fn cspace_revoke(fiber_id: u64, slot: usize) void {
    const cs = cspace_get(fiber_id) orelse return;
    cs.revoke(slot);
}

/// Check capability permission (C ABI)
pub export fn cspace_check_perm(fiber_id: u64, slot: usize, perm_bits: u8) bool {
    const cs = cspace_get(fiber_id) orelse return false;
    const cap = cs.lookup(slot) orelse return false;
    const perm: CapPerms = @bitCast(perm_bits);
    return cap.has_perm(perm);
}

// Unit tests
test "Capability creation and validation" {
    const cap = Capability{
        .cap_type = .Channel,
        .perms = .{ .read = true, .write = true },
        ._reserved = 0,
        .object_id = 0x1234,
        .bounds_start = 0x1000,
        .bounds_end = 0x2000,
    };

    try std.testing.expect(!cap.is_null());
    try std.testing.expect(cap.check_bounds(0x1500));
    try std.testing.expect(!cap.check_bounds(0x3000));
    try std.testing.expect(cap.has_perm(.{ .read = true }));
    try std.testing.expect(!cap.has_perm(.{ .execute = true }));
}

test "CSpace operations" {
    var cs = CSpace.init(42);

    const cap = Capability{
        .cap_type = .Memory,
        .perms = .{ .read = true, .write = true, .delegate = true },
        ._reserved = 0,
        .object_id = 0xABCD,
        .bounds_start = 0,
        .bounds_end = 0x1000,
    };

    // Grant capability
    const slot = try cs.grant(cap);
    try std.testing.expect(slot == 0);

    // Lookup capability
    const retrieved = cs.lookup(slot).?;
    try std.testing.expect(retrieved.object_id == 0xABCD);

    // Revoke capability
    cs.revoke(slot);
    try std.testing.expect(cs.lookup(slot) == null);
}

test "Delegation" {
    var cs1 = CSpace.init(1);
    var cs2 = CSpace.init(2);

    const cap = Capability{
        .cap_type = .Channel,
        .perms = .{ .read = true, .delegate = true, .copy = true },
        ._reserved = 0,
        .object_id = 0x5678,
        .bounds_start = 0,
        .bounds_end = 0xFFFF,
    };

    const slot1 = try cs1.grant(cap);

    // Copy delegation
    const slot2 = try cs1.delegate(slot1, &cs2, false);

    // Both should have the capability
    try std.testing.expect(cs1.lookup(slot1) != null);
    try std.testing.expect(cs2.lookup(slot2) != null);

    // Move delegation
    var cs3 = CSpace.init(3);
    const slot3 = try cs2.delegate(slot2, &cs3, true);

    // cs2 should no longer have it
    try std.testing.expect(cs2.lookup(slot2) == null);
    try std.testing.expect(cs3.lookup(slot3) != null);
}
