# 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: Network Transport Glue

# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# Rumpk Phase 36: Membrane Networking (Userland High-Speed IO)

import ion_client
# NOTE: Do NOT import ../../core/ion - it pulls in the KERNEL-ONLY 2MB memory pool!

proc console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.}
 
proc glue_print(s: string) =
  console_write(unsafeAddr s[0], csize_t(s.len))

# LwIP Imports
{.passC: "-Icore/rumpk/vendor/lwip/src/include".}
{.passC: "-Icore/rumpk/libs/membrane/include".}

# --- LwIP C Definitions ---
# Since we are building with 'any' OS and freestanding, we use Emit for C structural access
# but provide Nim wrappers for logic.

{.emit: """
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/pbuf.h"
#include "lwip/etharp.h"
#include "lwip/tcp.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "lwip/raw.h"
#include "lwip/icmp.h"
#include "lwip/inet_chksum.h"

#include <string.h>
#include "lwip/dhcp.h"

// Externs
extern int printf(const char *format, ...);


// If string.h is missing, we need the prototype for our clib.c implementation
void* memcpy(void* dest, const void* src, size_t n);

extern err_t etharp_output(struct netif *netif, struct pbuf *p, const ip4_addr_t *ipaddr);
// extern err_t netif_loopif_init(struct netif *netif);

const char* lwip_strerr(err_t err) { return "LwIP Error"; }

// --- PING IMPLEMENTATION (Phase 36c) ---
static struct raw_pcb *ping_pcb;
static u16_t ping_seq_num;
static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) {
    LWIP_UNUSED_ARG(arg);
    LWIP_UNUSED_ARG(pcb);
    if (p->tot_len >= sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr)) {
        printf("[Membrane] PING REPLY from %s: %d bytes\n", ipaddr_ntoa(addr), p->tot_len);
    }
    pbuf_free(p);
    return 1; // Eat the packet
}

static void ping_send(const ip_addr_t *addr) {
    if (!ping_pcb) {
        ping_pcb = raw_new(IP_PROTO_ICMP);
        if (ping_pcb) {
            raw_recv(ping_pcb, ping_recv, NULL);
            raw_bind(ping_pcb, IP_ADDR_ANY);
        }
    }
    if (!ping_pcb) return;

    struct pbuf *p = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + 32, PBUF_RAM);
    if (!p) return;

    struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)p->payload;
    ICMPH_TYPE_SET(iecho, ICMP_ECHO);
    ICMPH_CODE_SET(iecho, 0);
    iecho->chksum = 0;
    iecho->id = 0xAFAF;
    iecho->seqno = lwip_htons(++ping_seq_num);

    // Fill payload
    memset((char *)p->payload + sizeof(struct icmp_echo_hdr), 'A', 32);

    iecho->chksum = inet_chksum(iecho, p->len);
    
    raw_sendto(ping_pcb, p, addr);
    pbuf_free(p);
}
""".}

proc lwip_init*() {.importc: "lwip_init", cdecl.}
proc etharp_tmr*() {.importc: "etharp_tmr", cdecl.}
proc tcp_tmr*() {.importc: "tcp_tmr", cdecl.}
proc dhcp_fine_tmr() {.importc: "dhcp_fine_tmr", cdecl.}
proc dhcp_coarse_tmr() {.importc: "dhcp_coarse_tmr", cdecl.}
proc sys_now*(): uint32 {.importc: "sys_now", cdecl.}

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


# Forward declarations for LwIP callbacks
proc ion_linkoutput(netif: pointer, p: pointer): int32 {.exportc, cdecl.} =
  ## Callback: LwIP -> Netif -> ION Ring
  glue_print("[Membrane] Egress Packet\n")
  var pkt: IonPacket
  if not ion_user_alloc(addr pkt):
    return -1 # ERR_MEM

  # Copy pbuf chain into a single ION slab
  var offset = 0
  {.emit: """
    struct pbuf *curr = (struct pbuf *)`p`;
    while (curr != NULL) {
      if (`offset` + curr->len > 2000) break;
      
      // DEBUG: Verify payload
      // unsigned char* pl = (unsigned char*)curr->payload;
      // glue_print(" Payload Byte: ");
      // glue_print_hex((uint64_t)pl[0]);
      
      memcpy((void*)((uintptr_t)`pkt`.data + `offset` + 12), curr->payload, curr->len);
      `offset` += curr->len;
      curr = curr->next;
    }
  """.}
  
  pkt.len = uint16(offset) + 12
  
  if not ion_net_tx(pkt):
    ion_user_free(pkt)
    return -1 # ERR_IF
    
  return 0 # ERR_OK

proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} =
  let mac = ion_get_mac()
  glue_print("[Membrane] Configuring Interface with Hardware MAC\n")
  {.emit: """
    struct netif *ni = (struct netif *)`netif`;
    ni->name[0] = 'i';
    ni->name[1] = 'o';
    ni->output = etharp_output;
    ni->linkoutput = (netif_linkoutput_fn)ion_linkoutput;
    ni->mtu = 1500;
    ni->hwaddr_len = 6;
    ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP;
    
    // Set MAC from SysTable
    ni->hwaddr[0] = `mac`[0]; 
    ni->hwaddr[1] = `mac`[1]; 
    ni->hwaddr[2] = `mac`[2];
    ni->hwaddr[3] = `mac`[3]; 
    ni->hwaddr[4] = `mac`[4]; 
    ni->hwaddr[5] = `mac`[5];
  """.}
  return 0

# --- Membrane Globals ---
var g_netif: pointer
var last_tcp_tmr, last_arp_tmr, last_dhcp_fine, last_dhcp_coarse: uint32

var membrane_started = false

proc membrane_init*() {.exportc, cdecl.} =
  if membrane_started: return
  membrane_started = true

  glue_print("[Membrane] Initialization...\n")
  ion_user_init()
  
  # 1. LwIP Stack Init
  glue_print("[Membrane] Calling lwip_init()...\n")
  lwip_init()
  glue_print("[Membrane] lwip_init() returned.\n")
  {.emit: "printf(\"[Membrane] LwIP Byte Order: %d (LE=%d, BE=%d)\\n\", BYTE_ORDER, LITTLE_ENDIAN, BIG_ENDIAN);".}
  {.emit: "lwip_platform_diag(\"[Membrane] DIAG TEST: %s\\n\", \"OK\");".}

  # 2. Setup Netif
  {.emit: """
    static struct netif ni_static;
    ip4_addr_t ip, mask, gw;

    // Phase 38: DHCP Enabled
    IP4_ADDR(&ip, 0, 0, 0, 0); 
    IP4_ADDR(&mask, 0, 0, 0, 0);
    IP4_ADDR(&gw, 0, 0, 0, 0);
    
    netif_add(&ni_static, &ip, &mask, &gw, NULL, (netif_init_fn)ion_netif_init, (netif_input_fn)ethernet_input);
    netif_set_default(&ni_static);
    netif_set_up(&ni_static);
    
    dhcp_start(&ni_static);
    
    `g_netif` = &ni_static;
  """.}

  glue_print("[Membrane] Network Stack Operational (Waiting for DHCP IP...)\n")

var last_notified_ip: uint32 = 0
var dhcp_retried = false
var last_ping_time: uint32 = 0

proc glue_print_hex(v: uint64) =
  const hex_chars = "0123456789ABCDEF"
  var buf: array[20, char]
  buf[0] = '0'; buf[1] = 'x'
  var val = v
  for i in countdown(15, 0):
    buf[2+i] = hex_chars[int(val and 0xF)]
    val = val shr 4
  buf[18] = '\n'; buf[19] = '\0'
  console_write(addr buf[0], 20)

proc pump_membrane_stack*() {.exportc, cdecl.} =
  ## The Pulse of the Membrane. Call frequently to handle timers and RX.
  # glue_print("[Membrane] Pump\n")
  
  let now = sys_now()

  # 3. Check for IP (Avoid continuous Nim string allocation/leak)
  var ip_addr: uint32
  {.emit: "`ip_addr` = ip4_addr_get_u32(netif_ip4_addr((struct netif *)`g_netif`));".}
  if ip_addr != 0 and ip_addr != last_notified_ip:
    glue_print("[Membrane] IP STATUS CHANGE: ")
    glue_print_hex(uint64(ip_addr))
    glue_print("\n")
    last_notified_ip = ip_addr
 
  # Force DHCP Retry if no IP after 3 seconds
  if now > 3000 and not dhcp_retried and ip_addr == 0:
    dhcp_retried = true
    glue_print("[Membrane] Forcing DHCP Restart...\n")
    {.emit: "dhcp_start((struct netif *)`g_netif`);".}

  
  # 1. LwIP Timers (Raw API needs manual polling)
  if now - last_tcp_tmr >= 250:
    tcp_tmr()
    last_tcp_tmr = now
  if now - last_arp_tmr >= 5000:
    etharp_tmr()
    last_arp_tmr = now
  
  # Phase 37a: ICMP Ping Verification
  if now - last_ping_time > 1000:
     last_ping_time = now
     
     if ip_addr != 0:
         glue_print("[Membrane] PING: Sending ICMP Echo...\n")
         {.emit: """
           ip_addr_t gateway;
           IP4_ADDR(&gateway, 10, 0, 2, 2);
           ping_send(&gateway);
         """.}
  
  
  # DHCP Timers
  if now - last_dhcp_fine >= 500:
    # glue_print("[Membrane] DHCP Fine Timer\n")
    dhcp_fine_tmr()
    last_dhcp_fine = now
  if now - last_dhcp_coarse >= 60000:
    glue_print("[Membrane] DHCP Coarse Timer\n")
    dhcp_coarse_tmr()
    last_dhcp_coarse = now
    
  # 2. RX Ingress
  var pkt: IonPacket
  # glue_print("[Membrane] Exit Pump\n")
  while ion_net_rx(addr pkt):
    # glue_print("[Membrane] Ingress Packet\n")
    # DEBUG: Hex dump first 32 bytes (Disabled for Ping Test)
    # {.emit: """
    #   printf("[Membrane] RX Hex Dump (first 32 bytes):\n");
    #   for (int i = 0; i < 32 && i < `pkt`.len; i++) {
    #     printf("%02x ", `pkt`.data[i]);
    #     if ((i + 1) % 16 == 0) printf("\n");
    #   }
    #   printf("\n");
    # """.}
    # Pass to LwIP
    {.emit: """
      struct pbuf *p = pbuf_alloc(PBUF_RAW, `pkt`.len, PBUF_POOL);
      if (p != NULL) {
        if (`pkt`.data == NULL) {
           printf("[Membrane] ERROR: Ingress pkt.data is NULL!\n");
           pbuf_free(p);
        } else {
             // OFFSET FIX: Kernel already applied VirtIO offset (12 bytes) to pkt.data
             pbuf_take(p, (void*)((uintptr_t)`pkt`.data), `pkt`.len);
        
             if (netif_default->input(p, netif_default) != ERR_OK) {
               pbuf_free(p);
             }
        }
      }
    """.}
    ion_user_free(pkt)

# --- Glue Stubs (Phase 37) ---
# --- Glue Implementation (Phase 38) ---

# --- Glue Implementation (Phase 40) ---

# Global C definition for NexusSock to ensure visibility
{.emit: """
   typedef struct {
      NI fd;
      void* pcb;
      int state; // 0=CLOSED, 1=LISTEN, 2=CONNECTING, 3=ESTABLISHED
      unsigned char rx_buf[4096];
      NI rx_len;
      
      // Server Fields
      void* accepted_pcb;
      int accepted_pending;
   } NexusSock_C;
""".}

proc ion_tcp_connected(arg: pointer, pcb: pointer, err: int8): int8 {.exportc, cdecl.} =
  glue_print("[Membrane] TCP Connected!\n")
  {.emit: """
    NexusSock_C *s = (NexusSock_C *)`arg`;
    s->state = 3; // ESTABLISHED
  """.}
  return 0 

proc ion_tcp_sent(arg: pointer, pcb: pointer, len: uint16): int8 {.exportc, cdecl.} =
  return 0 

proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exportc, cdecl.} =
  if p == nil:
    glue_print("[Membrane] TCP Closed by remote\n")
    {.emit: """
      NexusSock_C *s = (NexusSock_C *)`arg`;
      s->state = 0; // CLOSED
    """.}
    return 0
    
  # Append data to rx_buf
  {.emit: """
    struct pbuf *curr = (struct pbuf *)`p`;
    NexusSock_C *s = (NexusSock_C *)`arg`;
    
    if (curr != NULL) {
      struct pbuf *q;
      for (q = curr; q != NULL; q = q->next) {
        if (s->rx_len + q->len < 4096) {
          memcpy(&s->rx_buf[s->rx_len], q->payload, q->len);
          s->rx_len += q->len;
        }
      }
      pbuf_free(curr);
      // We must cast pcb to struct tcp_pcb* for the macro/function
      tcp_recved((struct tcp_pcb *)`pcb`, curr->tot_len);
    }
  """.}
  return 0

proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc, cdecl.} =
  # Callback when a listening socket receives a new connection
  {.emit: """
     NexusSock_C *listener = (NexusSock_C *)`arg`;
     // Store the new PCB in the backlog slot
     // MVP: Only 1 pending connection allowed
     if (listener->accepted_pending == 0) {
       listener->accepted_pcb = `new_pcb`;
       listener->accepted_pending = 1;
       
       // Increase reference count? No, LwIP gives us ownership.
       // Important: We must not set callbacks yet? 
       // LwIP doc: "When a new connection arrives, the accept callback function is called. 
       // The new pcb is passed as a parameter."
       
       // We'll set callbacks later when libc performs the accept() syscall.
     } else {
       // Backlog full, reject?
       tcp_abort((struct tcp_pcb *)`new_pcb`);
       return -1; // ERR_ABRT
     }
  """.}
  return 0 

proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} =
  # Wire LwIP callbacks to a NexusSock
  {.emit: """
    NexusSock_C *ns = (NexusSock_C *)`sock`;
    struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`;
    ns->pcb = pcb;
    
    tcp_arg(pcb, ns); 
    tcp_recv(pcb, (tcp_recv_fn)ion_tcp_recv);
    tcp_sent(pcb, (tcp_sent_fn)ion_tcp_sent);
    // tcp_poll(pcb, ...);
  """.}

proc glue_connect*(sock: ptr NexusSock, ip: uint32, port: uint16): int {.exportc, cdecl.} =
  glue_print("[Membrane] glue_connect called\n")
  sock.state = CONNECTING
  sock.rx_len = 0
  {.emit: """
    struct tcp_pcb *pcb = tcp_new();
    if (pcb == NULL) return -1;
    
    // Wire up
    glue_setup_socket(`sock`, pcb);
    
    ip4_addr_t remote_ip;
    remote_ip.addr = `ip`; 
    
    tcp_connect(pcb, &remote_ip, `port`, (tcp_connected_fn)ion_tcp_connected);
  """.}
  return 0

proc glue_bind*(sock: ptr NexusSock, port: uint16): int {.exportc, cdecl.} =
  sock.state = LISTEN # Pre-state
  {.emit: """
    struct tcp_pcb *pcb = tcp_new();
    if (pcb == NULL) return -1;
    
    // Bind to ANY
    if (tcp_bind(pcb, IP_ADDR_ANY, `port`) != ERR_OK) {
       memp_free(MEMP_TCP_PCB, pcb);
       return -1;
    }
    
    // Update sock
    // glue_setup_socket checks validity, but here we just need to store PCB
    // Because we are not connecting, we don't set recv/sent yet? 
    // Actually we need tcp_arg for accept callback.
    
    NexusSock_C *ns = (NexusSock_C *)`sock`;
    ns->pcb = pcb;
    tcp_arg(pcb, ns);
  """.}
  return 0

proc glue_listen*(sock: ptr NexusSock): int {.exportc, cdecl.} =
  {.emit: """
    NexusSock_C *ns = (NexusSock_C *)`sock`;
    struct tcp_pcb *lpcb = tcp_listen((struct tcp_pcb *)ns->pcb);
    if (lpcb == NULL) return -1;
    
    ns->pcb = lpcb; // Update to listening PCB
    tcp_accept(lpcb, (tcp_accept_fn)ion_tcp_accept);
  """.}
  return 0

proc glue_accept_peek*(sock: ptr NexusSock): pointer {.exportc, cdecl.} =
  # Returns new PCB if pending, else nil
  var p: pointer = nil
  {.emit: """
    NexusSock_C *ns = (NexusSock_C *)`sock`;
    if (ns->accepted_pending) {
       `p` = ns->accepted_pcb;
       ns->accepted_pending = 0;
       ns->accepted_pcb = NULL;
    }
  """.}
  return p

proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} = 
  {.emit: """
    struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb;
    if (pcb == NULL) return -1;
    
    tcp_write(pcb, `buf`, `len`, TCP_WRITE_FLAG_COPY);
    tcp_output(pcb);
  """.}
  return len

proc glue_read*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
  if sock.rx_len == 0: return 0
  
  var to_read = len
  if to_read > sock.rx_len: to_read = sock.rx_len
  
  copyMem(buf, addr sock.rx_buf[0], uint64(to_read))
  
  # Shift buffer (Ring buffer would be better, but this is MVP)
  var remaining = sock.rx_len - to_read
  if remaining > 0:
    copyMem(addr sock.rx_buf[0], addr sock.rx_buf[to_read], uint64(remaining))
    
  sock.rx_len = remaining
  return to_read

proc glue_close*(sock: ptr NexusSock): int {.exportc, cdecl.} =
  {.emit: """
    struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb;
    if (pcb != NULL) {
      tcp_close(pcb);
      `sock`->pcb = NULL;
    }
  """.}
  return 0
