// SPDX-License-Identifier: LCL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
//
// This file is part of the Nexus Commonwealth.
// See legal/LICENSE_COMMONWEALTH.md for license terms.

//! Rumpk HAL: Reed-Solomon RAM Block Device (SPEC-103)
//!
//! Provides ECC-protected RAM storage using Reed-Solomon GF(2^8).
//! This is the "Cortex" - Space-Grade resilient memory.

const std = @import("std");

// =========================================================
// Galois Field GF(2^8) Arithmetic
// =========================================================
// Primitive polynomial: x^8 + x^4 + x^3 + x^2 + 1 (0x11D)

const GF_PRIM: u16 = 0x11D;

// Precomputed log and antilog tables for GF(2^8)
var gf_log: [256]u8 = undefined;
var gf_exp: [512]u8 = undefined;
var gf_initialized: bool = false;

fn gf_init() void {
    if (gf_initialized) return;

    var x: u16 = 1;
    for (0..255) |i| {
        gf_exp[i] = @truncate(x);
        gf_log[@as(usize, @as(u8, @truncate(x)))] = @truncate(i);
        x <<= 1;
        if (x & 0x100 != 0) {
            x ^= GF_PRIM;
        }
    }
    // Extend exp table for easier modulo
    for (255..512) |i| {
        gf_exp[i] = gf_exp[i - 255];
    }
    gf_log[0] = 0; // Undefined, but avoid issues
    gf_initialized = true;
}

fn gf_mul(a: u8, b: u8) u8 {
    if (a == 0 or b == 0) return 0;
    const log_a = gf_log[a];
    const log_b = gf_log[b];
    return gf_exp[@as(u16, log_a) + @as(u16, log_b)];
}

fn gf_div(a: u8, b: u8) u8 {
    if (b == 0) return 0; // Error: division by zero
    if (a == 0) return 0;
    const log_a = gf_log[a];
    const log_b = gf_log[b];
    return gf_exp[@as(u16, log_a) + 255 - @as(u16, log_b)];
}

fn gf_pow(a: u8, n: u8) u8 {
    if (n == 0) return 1;
    if (a == 0) return 0;
    const log_a: u16 = gf_log[a];
    return gf_exp[(log_a * @as(u16, n)) % 255];
}

fn gf_inv(a: u8) u8 {
    return gf_exp[255 - @as(u16, gf_log[a])];
}

// =========================================================
// Reed-Solomon Codec
// =========================================================

pub const SECTOR_SIZE: usize = 512;
pub const PARITY_SIZE: usize = 32; // Can correct 16 byte errors
pub const TOTAL_SIZE: usize = SECTOR_SIZE + PARITY_SIZE;

// Generator polynomial coefficients (precomputed for t=16)
var rs_generator: [PARITY_SIZE + 1]u8 = undefined;
var rs_generator_initialized: bool = false;

fn rs_init_generator() void {
    if (rs_generator_initialized) return;
    gf_init();

    // Build generator polynomial: prod(x - alpha^i) for i = 0..PARITY_SIZE-1
    rs_generator[0] = 1;
    for (1..PARITY_SIZE + 1) |i| {
        rs_generator[i] = 0;
    }

    for (0..PARITY_SIZE) |i| {
        const alpha_i = gf_pow(2, @truncate(i));
        // Multiply by (x - alpha^i)
        var j: usize = PARITY_SIZE;
        while (j > 0) : (j -= 1) {
            rs_generator[j] = rs_generator[j - 1] ^ gf_mul(rs_generator[j], alpha_i);
        }
        rs_generator[0] = gf_mul(rs_generator[0], alpha_i);
    }
    rs_generator_initialized = true;
}

/// Encode data with Reed-Solomon parity
pub fn rs_encode(data: []const u8, parity: *[PARITY_SIZE]u8) void {
    rs_init_generator();

    // Initialize parity to zero
    for (parity) |*p| p.* = 0;

    // Polynomial division
    for (data) |byte| {
        const feedback = byte ^ parity[0];
        // Shift parity register
        for (0..PARITY_SIZE - 1) |i| {
            parity[i] = parity[i + 1] ^ gf_mul(rs_generator[PARITY_SIZE - 1 - i], feedback);
        }
        parity[PARITY_SIZE - 1] = gf_mul(rs_generator[0], feedback);
    }
}

/// Calculate syndromes for error detection
fn rs_syndromes(data: []const u8, parity: []const u8) [PARITY_SIZE]u8 {
    var syndromes: [PARITY_SIZE]u8 = undefined;

    for (0..PARITY_SIZE) |i| {
        const alpha_i = gf_pow(2, @truncate(i));
        var s: u8 = 0;

        // Evaluate polynomial at alpha^i
        for (data) |byte| {
            s = gf_mul(s, alpha_i) ^ byte;
        }
        for (parity) |byte| {
            s = gf_mul(s, alpha_i) ^ byte;
        }
        syndromes[i] = s;
    }
    return syndromes;
}

/// Check if all syndromes are zero (no errors)
fn rs_check_syndromes(syndromes: []const u8) bool {
    for (syndromes) |s| {
        if (s != 0) return false;
    }
    return true;
}

/// Decode and repair data using Reed-Solomon
/// Returns true if data is valid (or was repaired), false if uncorrectable
pub fn rs_decode(data: []u8, parity: []u8) bool {
    rs_init_generator();

    const syndromes = rs_syndromes(data, parity);

    // No errors
    if (rs_check_syndromes(&syndromes)) return true;

    // TODO: Implement Berlekamp-Massey algorithm for error location
    // TODO: Implement Forney algorithm for error correction
    // For MVP, we detect but don't yet repair multi-byte errors

    // Simple single-byte error correction (simplified)
    // This is a placeholder - full RS decoding is complex

    return false; // Could not repair
}

// =========================================================
// RAM Block Device Interface
// =========================================================

var ram_base: [*]u8 = undefined;
var ram_size: usize = 0;
var ram_initialized: bool = false;

/// Initialize the RAM block device with a memory region
export fn ram_blk_init(base: [*]u8, size: usize) void {
    gf_init();
    rs_init_generator();
    ram_base = base;
    ram_size = size;
    ram_initialized = true;
}

/// Get the number of sectors available
export fn ram_blk_sector_count() u32 {
    if (!ram_initialized) return 0;
    return @truncate(ram_size / TOTAL_SIZE);
}

/// Write a sector with ECC protection
export fn ram_blk_write(sector: u32, data: [*]const u8) i32 {
    if (!ram_initialized) return -1;

    const offset = @as(usize, sector) * TOTAL_SIZE;
    if (offset + TOTAL_SIZE > ram_size) return -1;

    // Calculate parity
    var parity: [PARITY_SIZE]u8 = undefined;
    rs_encode(data[0..SECTOR_SIZE], &parity);

    // Write data + parity
    const dest = ram_base + offset;
    @memcpy(dest[0..SECTOR_SIZE], data[0..SECTOR_SIZE]);
    @memcpy(dest[SECTOR_SIZE..TOTAL_SIZE], &parity);

    return SECTOR_SIZE;
}

/// Read a sector with ECC verification/repair
export fn ram_blk_read(sector: u32, data: [*]u8) i32 {
    if (!ram_initialized) return -1;

    const offset = @as(usize, sector) * TOTAL_SIZE;
    if (offset + TOTAL_SIZE > ram_size) return -1;

    const src = ram_base + offset;

    // Read data + parity
    @memcpy(data[0..SECTOR_SIZE], src[0..SECTOR_SIZE]);
    var parity: [PARITY_SIZE]u8 = undefined;
    @memcpy(&parity, src[SECTOR_SIZE..TOTAL_SIZE]);

    // Verify and attempt repair
    var data_slice: [SECTOR_SIZE]u8 = undefined;
    @memcpy(&data_slice, data[0..SECTOR_SIZE]);

    if (!rs_decode(&data_slice, &parity)) {
        // TODO: Log the uncorrectable error
        return -2; // ECC_ERROR
    }

    // Copy repaired data back
    @memcpy(data[0..SECTOR_SIZE], &data_slice);

    return SECTOR_SIZE;
}

/// Scrub a sector (read and verify, repair if needed)
export fn ram_blk_scrub(sector: u32) i32 {
    var temp: [SECTOR_SIZE]u8 = undefined;
    const result = ram_blk_read(sector, &temp);
    if (result == SECTOR_SIZE) {
        // Re-write to fix any corrected errors
        return ram_blk_write(sector, &temp);
    }
    return result;
}

/// Scrub all sectors (patrol scrubbing)
export fn ram_blk_scrub_all() u32 {
    var errors: u32 = 0;
    const count = ram_blk_sector_count();
    for (0..count) |i| {
        if (ram_blk_scrub(@truncate(i)) < 0) {
            errors += 1;
        }
    }
    return errors;
}
