import ../../core/ion/memory
import ../../core/ring

# Fixed address for the SysTable (provided by Carrier)
const SYS_TABLE_ADDR = 0x83000000'u64

type
  SysTable = object
    magic*: uint32
    s_rx*: pointer
    s_tx*: pointer
    s_event*: pointer

# The Ring where the Kernel (Switch) pushes packets for this app
var membrane_rx_ring_static: RingBuffer[IonPacket, 256]
var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256]

proc ion_user_init*() {.exportc.} =
  when defined(is_membrane):
    let sys = cast[ptr SysTable](SYS_TABLE_ADDR)
    membrane_rx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_rx)
  else:
    membrane_rx_ring_static.init()
    membrane_rx_ring_ptr = addr membrane_rx_ring_static

proc ion_user_alloc*(out_pkt: ptr IonPacket): bool {.exportc.} =
  ## Allocate a slab for the application to write into.
  when defined(is_membrane):
    # TODO: Implement allocation via Command Ring or shared pool
    return false
  else:
    var pkt = ion_alloc()
    if pkt.data == nil:
      return false
    out_pkt[] = pkt
    return true

proc ion_user_free*(pkt: IonPacket) {.exportc.} =
  ## Return a slab to the pool.
  when defined(is_membrane):
    # TODO: Implement free via Command Ring
    discard
  else:
    ion_free(pkt)

proc ion_user_tx*(pkt: IonPacket): bool {.exportc.} =
  ## Push a packet to the Transmission Ring.
  when defined(is_membrane):
    let sys = cast[ptr SysTable](SYS_TABLE_ADDR)
    let tx = cast[ptr RingBuffer[IonPacket, 256]](sys.s_tx)
    return tx[].push(pkt)
  else:
    return ion_tx_push(pkt)

proc ion_user_rx*(out_pkt: ptr IonPacket): bool {.exportc.} =
  ## Pop a packet from the Application's RX Ring.
  if membrane_rx_ring_ptr == nil: return false
  if membrane_rx_ring_ptr[].isEmpty: return false
  let (ok, pkt) = membrane_rx_ring_ptr[].pop()
  if not ok: return false
  out_pkt[] = pkt
  return true
