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

## Rumpk Layer 1: ION Slab Allocator

# ION Memory Manager
# The "Slab Allocator" for Zero-Copy IO

import ../ring

proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}

var NEWLINE_BUF: array[2, char] = ['\n', '\0']

proc dbg(s: cstring) {.inline.} =
  if s != nil:
    var i = 0
    let p = cast[ptr UncheckedArray[char]](s)
    while p[i] != '\0': inc i
    console_write(cast[pointer](s), csize_t(i))
  console_write(addr NEWLINE_BUF[0], csize_t(1))

const
  SLAB_SIZE* = 2048  # Max Packet Size (Ethernet Frame + Headroom)
  POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM)
  POOL_ALIGN* = 4096 # VirtIO/Page Alignment

  SYSTABLE_BASE = when defined(arm64): 0x50000000'u64
                  else: 0x83000000'u64
  USER_SLAB_OFFSET = 0x10000'u64   # Offset within SYSTABLE
  USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET  # 0x83010000
  USER_SLAB_COUNT = 512           # 512 packets to cover RX Ring (256) + TX
  USER_PKT_SIZE = 2048            # 2KB per packet
  USER_BITMAP_ADDR = SYSTABLE_BASE + 0x100

type
  # The Physical Token representing a packet
  IonPacket* = object
    data*: ptr UncheckedArray[byte] # Virtual Address of payload
    phys*: uint64                   # Physical Address (For VirtIO/DMA)
    len*: uint16                    # Actual data length
    id*: uint16                     # The Token ID (Slab Index)

                                    # The Monolithic Memory Pool
  PacketPool = object
    # We use 'align' to ensure the buffer starts on a page boundary for VirtIO
    buffer {.align: POOL_ALIGN.}: array[SLAB_SIZE * POOL_COUNT, byte]
    free_ring: RingBuffer[uint16, POOL_COUNT] # Stores IDs of free slabs
    base_phys: uint64

var global_tx_ring*: RingBuffer[IonPacket, 256]
var global_pool: PacketPool

proc ion_pool_init*() {.exportc.} =
  ## Initialize the DMA Pool with REAL Physical Address
  dbg("[ION] Initializing Pool...")

  # 1. Get the VIRTUAL address of the static buffer
  dbg("[ION] Step 1: Getting virt addr...")
  let virt_addr = cast[uint64](addr global_pool.buffer[0])

  # 2. Translate to PHYSICAL (Identity Mapped for Phase 7)
  dbg("[ION] Step 2: Setting base phys...")
  global_pool.base_phys = virt_addr

  # Tracing for Phase 37
  proc kprint_hex(v: uint64) {.importc, cdecl.}
  dbg("[ION] Pool Base Phys: ")
  kprint_hex(global_pool.base_phys)
  dbg("")

  dbg("[ION] Step 3: Ring Init (free_ring)...")
  dbg("  3a: About to call init()")
  global_pool.free_ring.init()
  dbg("  3b: free_ring init complete")
  dbg("[ION] Step 4: Ring Init (tx_ring)...")
  global_tx_ring.init()
  dbg("  4a: tx_ring init complete")

  # Fill the free ring with all indices [0..1023]
  dbg("[ION] Step 5: Filling Slabs...")
  var count = 0
  for i in 0 ..< POOL_COUNT:
    if global_pool.free_ring.push(uint16(i)):
      inc count

  dbg("[ION] Pool Ready.")



proc ion_alloc*(): IonPacket {.exportc.} =
  ## O(1) Allocation. Returns an empty packet struct.
  ## If OOM, returns packet with data = nil
  var pkt: IonPacket

  let (ok, idx) = global_pool.free_ring.pop()
  if not ok:
    dbg("[ION] ALLOC FAILED (Empty Ring?)")
    pkt.data = nil
    return pkt

  pkt.id = idx
  pkt.len = 0

  # Calculate Virtual Address
  let offset = int(idx) * SLAB_SIZE
  pkt.data = cast[ptr UncheckedArray[byte]](addr global_pool.buffer[offset])

  # Calculate Physical Address (1:1 map for Phase 7)
  pkt.phys = global_pool.base_phys + uint64(offset)

  return pkt

proc ion_free*(pkt: IonPacket) {.exportc.} =
  ## O(1) Free. Returns the token to the ring.
  if pkt.data == nil: return

  if (pkt.id and 0x8000) != 0:
    # User Slab - Clear shared bitmap
    let slotIdx = pkt.id and 0x7FFF
    if slotIdx >= USER_SLAB_COUNT: return
    let bitmap = cast[ptr array[16, byte]](USER_BITMAP_ADDR)
    let byteIdx = int(slotIdx) div 8
    let bitIdx = int(slotIdx) mod 8
    let mask = byte(1 shl bitIdx)
    bitmap[byteIdx] = bitmap[byteIdx] and (not mask)
    return

  discard global_pool.free_ring.push(pkt.id)

# Helper for C/Zig Interop (Pure Pointers)
# Return physical address of a allocated block, put ID in out_id
proc ion_alloc_raw*(out_id: ptr uint16): uint64 {.exportc, cdecl.} =
  let pkt = ion_alloc()
  if pkt.data == nil: return 0
  out_id[] = pkt.id
  return pkt.phys

proc ion_free_raw*(id: uint16) {.exportc, cdecl.} =
  var pkt: IonPacket
  pkt.id = id
  # We don't reconstruct data/phys for free, just push ID
  # But for safety we might check bounds? Ring handles it.
  pkt.data = cast[ptr UncheckedArray[byte]](1) # Dummy non-nil
  ion_free(pkt)

proc ion_get_virt*(id: uint16): ptr byte {.exportc.} =
  if (id and 0x8000) != 0:
    let idx = id and 0x7FFF
    let offset = int(idx) * SLAB_SIZE
    return cast[ptr byte](USER_SLAB_BASE + uint64(offset))
  let offset = int(id) * SLAB_SIZE
  return addr global_pool.buffer[offset]

proc ion_get_phys*(id: uint16): uint64 {.exportc.} =
  if (id and 0x8000) != 0:
    let idx = id and 0x7FFF
    let offset = int(idx) * SLAB_SIZE
    return USER_SLAB_BASE + uint64(offset)
  let offset = int(id) * SLAB_SIZE
  return global_pool.base_phys + uint64(offset)

# =========================================================
# The Global TX Ring (Multiplexing)
# =========================================================


proc ion_tx_init*() {.exportc.} =
  global_tx_ring.init()

proc ion_tx_push*(pkt: IonPacket): bool {.exportc.} =
  if global_tx_ring.push(pkt):
     # dbg("[ION TX] Pushed")
     return true
  dbg("[ION TX] PUSH FAILED (Global Ring Full)")
  return false

proc ion_tx_pop*(out_id: ptr uint16, out_len: ptr uint16): bool {.exportc.} =
  if global_tx_ring.isEmpty:
    return false

  let (ok, pkt) = global_tx_ring.pop()
  if not ok: return false

  out_id[] = pkt.id
  out_len[] = pkt.len
  dbg("[ION TX] Popped Packet for VirtIO")
  return true

# =========================================================
# User-Visible Slab Allocator (Shared Memory)
# =========================================================
# NOTE: This allocator provides buffers in the SYSTABLE shared region
# (0x83010000+) which is mapped into both kernel and userland page tables.
# Used for network packet egress from userland.

# NOTE: Constants moved to top


# var user_slab_bitmap: array[USER_SLAB_COUNT, bool] # REMOVED: Use Shared Bitmap

proc ion_user_slab_init*() {.exportc.} =
  ## Initialize shared user slab bitmap (all free)
  let bitmap = cast[ptr array[64, byte]](USER_BITMAP_ADDR)
  for i in 0 ..< 64:
    bitmap[i] = 0

proc ion_alloc_shared*(out_id: ptr uint16): uint64 {.exportc, cdecl.} =
  ## Allocate a buffer from the user-visible slab (Kernel Side, Shared Bitmap)
  let bitmap = cast[ptr array[64, byte]](USER_BITMAP_ADDR)

  for byteIdx in 0 ..< 64:
    if bitmap[byteIdx] != 0xFF:
      for bitIdx in 0 ..< 8:
        let mask = byte(1 shl bitIdx)
        if (bitmap[byteIdx] and mask) == 0:
          # Found free
          bitmap[byteIdx] = bitmap[byteIdx] or mask
          let idx = byteIdx * 8 + bitIdx
          if idx >= USER_SLAB_COUNT: return 0

          out_id[] = uint16(idx) or 0x8000
          return USER_SLAB_BASE + uint64(idx) * USER_PKT_SIZE
  return 0
