# 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.

## Nexus Membrane: POSIX Core Shim

# Markus Maiwald (Architect) | Voxis Forge (AI)
# libc.nim - Sovereign Libc for Nexus
# (C) 2026 Markus Maiwald

import ion_client
import net_glue


# memcpy removed to avoid C header conflict



proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int =
  var res: int
  let n = cast[uint64](nr)
  let v0 = a0
  let v1 = a1
  let v2 = a2
  {.emit: """
    register unsigned long a7 __asm__("a7") = `n`;
    register unsigned long a0_ __asm__("a0") = `v0`;
    register unsigned long a1_ __asm__("a1") = `v1`;
    register unsigned long a2_ __asm__("a2") = `v2`;
    __asm__ volatile("ecall" : "+r"(a0_) : "r"(a7), "r"(a1_), "r"(a2_) : "memory");
    `res` = (int)a0_;
  """.}
  return res

# --- LIBC IO SHIMS ---

when not defined(RUMPK_KERNEL):
  # write and execv are defined in clib.c/libnexus.a
  proc write*(fd: int, buf: pointer, count: uint64): int {.importc: "write", cdecl.}
  proc read*(fd: int, buf: pointer, count: uint64): int {.importc: "read", cdecl.}
  proc open*(path: cstring, flags: int = 0): int {.importc: "open", cdecl.}
  proc close*(fd: int): int {.importc: "close", cdecl.}
  proc execv*(path: cstring, argv: pointer): int {.importc: "execv", cdecl.}
  
  # Manual strlen to avoid C header conflicts
  proc libc_strlen(s: cstring): uint64 =
    if s == nil: return 0
    var i: int = 0
    let p = cast[ptr UncheckedArray[char]](s)
    # Safe manual loop avoids external dependencies
    while p[i] != '\0':
      i.inc
    return uint64(i)

  proc print*(s: cstring) =
    let len = libc_strlen(s)
    if len > 0: discard write(1, s, len)

  proc print*(s: string) =
    if s.len > 0: discard write(1, unsafeAddr s[0], uint64(s.len))

  proc readdir*(buf: pointer, max_len: uint64): int {.exportc, cdecl.} =
    return int(syscall(0x202, cast[uint64](buf), max_len))

  proc exit*(status: int) {.exportc, cdecl.} =
    discard syscall(0x01, uint64(status))
    while true: discard 

  proc yield_fiber*() {.exportc: "yield", cdecl.} =
    discard syscall(0x100, 0)

  proc pump_membrane_stack*() {.importc, cdecl.}
  proc membrane_init*() {.importc, cdecl.}
  proc ion_user_wait_multi*(mask: uint64): int32 {.importc, cdecl.}
  
  proc pledge*(promises: uint64): int {.exportc, cdecl.} =
    return int(syscall(0x101, promises))

  proc spawn*(entry: pointer, arg: uint64): int {.exportc, cdecl.} =
    return int(syscall(0x500, cast[uint64](entry), arg))

  proc join*(fid: int): int {.exportc, cdecl.} =
    return int(syscall(0x501, uint64(fid)))

  proc kexec*(entry: pointer): int {.exportc, cdecl.} =
    return int(syscall(0x600, cast[uint64](entry)))

  proc upgrade*(id: int, path: cstring): int {.exportc, cdecl.} =
    # Deprecated: Use kexec directly
    return -1 

  proc get_vfs_listing*(): seq[string] =
    var buf: array[4096, char]
    let n = readdir(addr buf[0], 4096)
    if n <= 0: return @[]
    
    result = @[]
    var current = ""
    for i in 0..<n:
      if buf[i] == '\n':
        if current.len > 0:
          result.add(current)
          current = ""
      else:
        current.add(buf[i])
    if current.len > 0: result.add(current)

  # Surface API (Glyph)
  proc sys_surface_create*(width, height: int): int {.exportc, cdecl.} =
    return int(syscall(0x300, uint64(width), uint64(height)))

  proc sys_surface_flip*(surf_id: int = 0) {.exportc, cdecl.} =
    discard syscall(0x301, uint64(surf_id))

  proc sys_surface_get_ptr*(surf_id: int): pointer {.exportc, cdecl.} =
    return cast[pointer](syscall(0x302, uint64(surf_id)))

# --- NETWORK SHIMS (Membrane) ---

const 
  MAX_SOCKS = 32
  FD_OFFSET = 3
  # Syscalls
  SYS_SOCK_SOCKET = 0x900
  SYS_SOCK_BIND   = 0x901
  SYS_SOCK_CONNECT= 0x902
  SYS_SOCK_LISTEN = 0x903
  SYS_SOCK_ACCEPT = 0x904

when defined(RUMPK_KERNEL):
  # KERNEL IMPLEMENTATION
  type
    SockState = enum
      CLOSED, LISTEN, CONNECTING, ESTABLISHED, FIN_WAIT

    NexusSock = object
      fd: int
      pcb: pointer
      state: SockState
      rx_buf: array[4096, byte]
      rx_len: int
      accepted_pcb: pointer
      accepted_pending: int32

  var g_sockets: array[MAX_SOCKS, NexusSock]
  var g_sock_used: array[MAX_SOCKS, bool]

  proc membrane_init*() {.importc, cdecl.}
  proc pump_membrane_stack*() {.importc, cdecl.}
  proc rumpk_yield_internal() {.importc, cdecl.}

  proc glue_connect(sock: ptr NexusSock, ip: uint32, port: uint16): int {.importc, cdecl.}
  proc glue_bind(sock: ptr NexusSock, port: uint16): int {.importc, cdecl.}
  proc glue_listen(sock: ptr NexusSock): int {.importc, cdecl.}
  proc glue_accept_peek(sock: ptr NexusSock): pointer {.importc, cdecl.}
  proc glue_setup_socket(sock: ptr NexusSock, pcb: pointer) {.importc, cdecl.}
  proc glue_write(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.}
  proc glue_read(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.}
  proc glue_close(sock: ptr NexusSock): int {.importc, cdecl.}

  const
    MAX_FILES = 16
    FILE_FD_START = 100

  type
    FDType = enum FD_NONE, FD_SOCKET, FD_FILE
    FD_Entry = object
      kind: FDType
      path: array[64, char] # For files

  var g_fd_table: array[256, FD_Entry]

  proc alloc_sock(): int =
    for i in 0..<MAX_SOCKS:
      if not g_sock_used[i]:
        let fd = i + FD_OFFSET
        g_sock_used[i] = true
        g_sockets[i].fd = fd
        g_sockets[i].pcb = nil
        g_sockets[i].state = CLOSED
        g_sockets[i].rx_len = 0
        g_sockets[i].accepted_pcb = nil
        g_sockets[i].accepted_pending = 0
        
        g_fd_table[fd].kind = FD_SOCKET
        return i
    return -1

  proc get_sock(fd: int): ptr NexusSock =
    let idx = fd - FD_OFFSET
    if idx < 0 or idx >= MAX_SOCKS: return nil
    if not g_sock_used[idx]: return nil
    if g_fd_table[fd].kind != FD_SOCKET: return nil
    return addr g_sockets[idx]

  proc libc_is_socket_fd*(fd: int): bool {.exportc: "libc_is_socket_fd", cdecl.} =
    if fd < 0 or fd >= 256: return false
    return g_fd_table[fd].kind == FD_SOCKET

  proc libc_impl_socket*(domain, sock_type, proto: int): int {.exportc: "libc_impl_socket", cdecl.} = 
    let idx = alloc_sock()
    if idx < 0: return -1
    return idx + FD_OFFSET

  proc libc_impl_bind*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "libc_impl_bind", cdecl.} =
    let sock = get_sock(fd)
    if sock == nil: return -1
    let port_ptr = cast[ptr uint16](cast[uint64](addr_ptr) + 2)
    return glue_bind(sock, port_ptr[])

  proc libc_impl_listen*(fd: int, backlog: int): int {.exportc: "libc_impl_listen", cdecl.} =
    let sock = get_sock(fd)
    if sock == nil: return -1
    return glue_listen(sock)

  proc libc_impl_accept*(fd: int, addr_ptr: pointer, len_ptr: pointer): int {.exportc: "libc_impl_accept", cdecl.} =
    let listener = get_sock(fd)
    if listener == nil: return -1
    while true:
      pump_membrane_stack()
      let client_pcb = glue_accept_peek(listener)
      if client_pcb != nil:
         let client_idx = alloc_sock()
         if client_idx < 0: return -1
         let client = addr g_sockets[client_idx]
         glue_setup_socket(client, client_pcb)
         client.state = ESTABLISHED
         return client.fd
      rumpk_yield_internal()

  proc libc_impl_connect*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "libc_impl_connect", cdecl.} =
    let sock = get_sock(fd)
    if sock == nil: return -1
    let ip_ptr = cast[ptr uint32](cast[uint64](addr_ptr) + 4)
    let port_ptr = cast[ptr uint16](cast[uint64](addr_ptr) + 2)
    let res = glue_connect(sock, ip_ptr[], port_ptr[])
    while sock.state == CONNECTING:
      pump_membrane_stack()
      rumpk_yield_internal()
    if sock.state == ESTABLISHED: return 0
    return -1

  proc libc_impl_recv*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_recv", cdecl.} = 
    let sock = get_sock(fd)
    if sock == nil: return -1
    while true:
      pump_membrane_stack()
      let n = glue_read(sock, buf, int(count))
      if n > 0: return n
      if sock.state == CLOSED: return 0
      rumpk_yield_internal() 

  proc libc_impl_send*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_send", cdecl.} = 
    let sock = get_sock(fd)
    if sock == nil: return -1
    let res = glue_write(sock, buf, int(count))
    pump_membrane_stack() 
    return res

  proc libc_impl_close_socket*(fd: int): int {.exportc: "libc_impl_close_socket", cdecl.} =
    let sock = get_sock(fd)
    if sock == nil: return -1
    discard glue_close(sock)
    let idx = fd - FD_OFFSET
    g_sock_used[idx] = false
    return 0

  # --- VFS SHIMS ---
  # These route POSIX file calls to our Sovereign File System (SFS)
  proc sfs_open_file*(path: cstring, flags: int): int32 {.importc, cdecl.}
  proc sfs_read_file*(path: cstring, dest: pointer, max_len: int): int {.importc, cdecl.}
  proc sfs_write_file*(name: cstring, data: pointer, data_len: int) {.importc, cdecl.}

  proc libc_impl_open*(path: cstring, flags: int): int {.exportc: "libc_impl_open", cdecl.} =
    # Find free File FD
    for i in FILE_FD_START..<255:
      if g_fd_table[i].kind == FD_NONE:
        g_fd_table[i].kind = FD_FILE
        let p = cast[ptr UncheckedArray[char]](path)
        var j = 0
        while p[j] != '\0' and j < 63:
          g_fd_table[i].path[j] = p[j]
          j += 1
        g_fd_table[i].path[j] = '\0'
        return i
    return -1

  proc libc_impl_read*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_read", cdecl.} =
    if fd == 0: return int(syscall(0x203, 0, cast[uint64](buf), count))
    
    if fd >= 0 and fd < 256:
      if g_fd_table[fd].kind == FD_FILE:
        let path = cast[cstring](addr g_fd_table[fd].path[0])
        return sfs_read_file(path, buf, int(count))
      elif g_fd_table[fd].kind == FD_SOCKET:
        return libc_impl_recv(fd, buf, count)
    return -1

  proc libc_impl_write*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_write", cdecl.} =
    if fd == 1 or fd == 2: return int(syscall(0x204, uint64(fd), cast[uint64](buf), count))

    if fd >= 0 and fd < 256:
      if g_fd_table[fd].kind == FD_FILE:
        let path = cast[cstring](addr g_fd_table[fd].path[0])
        sfs_write_file(path, buf, int(count))
        return int(count)
      elif g_fd_table[fd].kind == FD_SOCKET:
        return libc_impl_send(fd, buf, count)
    return -1

  proc libc_impl_close*(fd: int): int {.exportc: "libc_impl_close", cdecl.} =
    if fd < 0 or fd >= 256: return -1
    if g_fd_table[fd].kind == FD_SOCKET:
      discard libc_impl_close_socket(fd)
    g_fd_table[fd].kind = FD_NONE
    return 0

else:
  # USER WRAPPERS
  proc socket*(domain, sock_type, protocol: int): int {.exportc, cdecl.} = 
     return int(syscall(SYS_SOCK_SOCKET, uint64(domain), uint64(sock_type), uint64(protocol)))

  proc bind_socket*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "bind", cdecl.} =
     return int(syscall(SYS_SOCK_BIND, uint64(fd), cast[uint64](addr_ptr), uint64(len)))

  proc connect*(fd: int, addr_ptr: pointer, len: int): int {.exportc, cdecl.} =
     return int(syscall(SYS_SOCK_CONNECT, uint64(fd), cast[uint64](addr_ptr), uint64(len)))

  proc listen*(fd: int, backlog: int): int {.exportc, cdecl.} =
     return int(syscall(SYS_SOCK_LISTEN, uint64(fd), uint64(backlog)))

  proc accept*(fd: int, addr_ptr: pointer, len_ptr: pointer): int {.exportc, cdecl.} =
     return int(syscall(SYS_SOCK_ACCEPT, uint64(fd), cast[uint64](addr_ptr), cast[uint64](len_ptr)))

  proc send*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} = 
     return int(syscall(0x204, uint64(fd), cast[uint64](buf), count))

  proc recv*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} = 
     return int(syscall(0x203, uint64(fd), cast[uint64](buf), count))

# =========================================================
# lwIP Syscall Bridge (SPEC-701, SPEC-805)
# =========================================================

# The Graft: These C-compatible exports provide the kernel interface
# required by sys_arch.c without pulling in kernel-only code.

proc syscall_get_time_ns*(): uint64 {.exportc, cdecl.} =
  ## Get monotonic time in nanoseconds from kernel
  ## Used by lwIP's sys_now() for timer management
  # TODO: Add dedicated syscall 0x700 for TIME
  # For now, use rdtime directly (architecture-specific)
  var ticks: uint64
  {.emit: """
    #if defined(__riscv)
      __asm__ volatile ("rdtime %0" : "=r"(`ticks`));
      // RISC-V QEMU virt: 10MHz timer -> 100ns per tick
      `ticks` = `ticks` * 100;
    #elif defined(__aarch64__)
      __asm__ volatile ("mrs %0, cntvct_el0" : "=r"(`ticks`));
      // ARM64: Assume 1GHz for now (should read cntfrq_el0)
      // `ticks` = `ticks`;
    #else
      `ticks` = 0;
    #endif
  """.}
  return ticks


proc syscall_get_random*(): uint32 {.exportc, cdecl.} =
  ## Generate cryptographically strong random number for TCP ISN
  ## Implementation: SipHash-2-4(MonolithKey, Time || CycleCount)
  ## Per SPEC-805: Hash Strategy
  
  let sys = get_sys_table()
  
  # Get high-resolution time
  let time_ns = syscall_get_time_ns()
  
  # Mix time with itself (upper/lower bits)
  var mix_data: array[16, byte]
  copyMem(addr mix_data[0], unsafeAddr time_ns, 8)
  
  # Add cycle counter for additional entropy
  var cycles: uint64
  {.emit: """
    #if defined(__riscv)
      __asm__ volatile ("rdcycle %0" : "=r"(`cycles`));
    #else
      `cycles` = 0;
    #endif
  """.}
  copyMem(addr mix_data[8], unsafeAddr cycles, 8)
  
  # Use SipHash with system key (SPEC-805)
  # TODO: Use actual Monolith key when available
  var key: array[16, byte]
  for i in 0..<16:
    key[i] = byte(i xor 0xAA) # Temporary key (Phase 39: Use Monolith)
  
  var hash_out: array[16, byte]
  if sys.fn_siphash != nil:
    sys.fn_siphash(addr key, addr mix_data[0], 16, addr hash_out)
    # Return first 32 bits
    var rnd: uint32
    copyMem(addr rnd, addr hash_out[0], 4)
    return rnd
  else:
    # Fallback: XOR mixing if SipHash unavailable
    return uint32(time_ns xor (time_ns shr 32) xor cycles)

proc syscall_panic*() {.exportc, cdecl, noreturn.} =
  ## Trigger kernel panic from lwIP assertion failure
  ## Routes to kernel's EXIT syscall
  discard syscall(0x01, 255) # EXIT with error code 255
  while true: discard # noreturn

