# 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 Core: Pseudo-Terminal (PTY) Subsystem
##
## Provides a POSIX-like PTY interface for terminal emulation.
## Master fd is held by terminal emulator, slave fd by shell.
##
## Phase 40: The Soul Bridge (PTY Implementation)

import ../libs/membrane/term

const
  MAX_PTYS* = 8
  PTY_BUFFER_SIZE* = 4096
  
  # File descriptor ranges
  PTY_MASTER_BASE* = 100  # Master fds: 100-107
  PTY_SLAVE_BASE* = 200   # Slave fds: 200-207

type
  LineMode* = enum
    lmRaw,      # No processing (binary mode)
    lmCanon     # Canonical mode (line buffering, echo)
  
  PtyPair* = object
    active*: bool
    id*: int
    
    # Buffers (bidirectional)
    master_to_slave*: array[PTY_BUFFER_SIZE, byte]
    mts_head*, mts_tail*: int
    
    slave_to_master*: array[PTY_BUFFER_SIZE, byte]
    stm_head*, stm_tail*: int
    
    # Line discipline
    mode*: LineMode
    echo*: bool
    
    # Window size
    rows*, cols*: int

var ptys*: array[MAX_PTYS, PtyPair]
var next_pty_id: int = 0

# --- Logging ---
proc kprint(s: cstring) {.importc, cdecl.}
proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.}

proc pty_init*() {.exportc, cdecl.} =
  for i in 0 ..< MAX_PTYS:
    ptys[i].active = false
    ptys[i].id = -1
  next_pty_id = 0
  kprintln("[PTY] Subsystem Initialized")

proc pty_alloc*(): int {.exportc, cdecl.} =
  ## Allocate a new PTY pair. Returns PTY ID or -1 on failure.
  for i in 0 ..< MAX_PTYS:
    if not ptys[i].active:
      ptys[i].active = true
      ptys[i].id = next_pty_id
      ptys[i].mts_head = 0
      ptys[i].mts_tail = 0
      ptys[i].stm_head = 0
      ptys[i].stm_tail = 0
      ptys[i].mode = lmCanon
      ptys[i].echo = true
      ptys[i].rows = 37
      ptys[i].cols = 100
      next_pty_id += 1
      kprint("[PTY] Allocated ID=")
      kprint_hex(uint64(ptys[i].id))
      kprintln("")
      return ptys[i].id
  kprintln("[PTY] ERROR: Max PTYs allocated")
  return -1

proc pty_get_master_fd*(pty_id: int): int =
  ## Get the master file descriptor for a PTY.
  if pty_id < 0 or pty_id >= MAX_PTYS: return -1
  if not ptys[pty_id].active: return -1
  return PTY_MASTER_BASE + pty_id

proc pty_get_slave_fd*(pty_id: int): int =
  ## Get the slave file descriptor for a PTY.
  if pty_id < 0 or pty_id >= MAX_PTYS: return -1
  if not ptys[pty_id].active: return -1
  return PTY_SLAVE_BASE + pty_id

proc is_pty_master_fd*(fd: int): bool =
  return fd >= PTY_MASTER_BASE and fd < PTY_MASTER_BASE + MAX_PTYS

proc is_pty_slave_fd*(fd: int): bool =
  return fd >= PTY_SLAVE_BASE and fd < PTY_SLAVE_BASE + MAX_PTYS

proc get_pty_from_fd*(fd: int): ptr PtyPair =
  if is_pty_master_fd(fd):
    let idx = fd - PTY_MASTER_BASE
    if ptys[idx].active: return addr ptys[idx]
  elif is_pty_slave_fd(fd):
    let idx = fd - PTY_SLAVE_BASE
    if ptys[idx].active: return addr ptys[idx]
  return nil

# --- Buffer Operations ---

proc ring_push(buf: var array[PTY_BUFFER_SIZE, byte], head, tail: var int, data: byte): bool =
  let next = (tail + 1) mod PTY_BUFFER_SIZE
  if next == head: return false  # Buffer full
  buf[tail] = data
  tail = next
  return true

proc ring_pop(buf: var array[PTY_BUFFER_SIZE, byte], head, tail: var int): int =
  if head == tail: return -1  # Buffer empty
  let b = int(buf[head])
  head = (head + 1) mod PTY_BUFFER_SIZE
  return b

proc ring_count(head, tail: int): int =
  if tail >= head:
    return tail - head
  else:
    return PTY_BUFFER_SIZE - head + tail

# --- I/O Operations ---

proc pty_write_master*(fd: int, data: ptr byte, len: int): int =
  ## Write to master (goes to slave input). Called by terminal emulator.
  let pty = get_pty_from_fd(fd)
  if pty == nil: return -1
  
  var written = 0
  for i in 0 ..< len:
    let b = cast[ptr UncheckedArray[byte]](data)[i]
    if ring_push(pty.master_to_slave, pty.mts_head, pty.mts_tail, b):
      written += 1
    else:
      break  # Buffer full
  return written

proc pty_read_master*(fd: int, data: ptr byte, len: int): int =
  ## Read from master (gets slave output). Called by terminal emulator.
  let pty = get_pty_from_fd(fd)
  if pty == nil: return -1
  
  var read_count = 0
  let buf = cast[ptr UncheckedArray[byte]](data)
  for i in 0 ..< len:
    let b = ring_pop(pty.slave_to_master, pty.stm_head, pty.stm_tail)
    if b < 0: break
    buf[i] = byte(b)
    read_count += 1
  return read_count

proc pty_write_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.} =
  ## Write to slave (output from shell). Goes to master read buffer.
  ## Also renders to FB terminal.
  let pty = get_pty_from_fd(fd)
  if pty == nil: return -1
  
  var written = 0
  let buf = cast[ptr UncheckedArray[byte]](data)
  for i in 0 ..< len:
    let b = buf[i]
    
    # Push to slave-to-master buffer (for terminal emulator)
    if ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, b):
      written += 1
      
      # Also render to FB terminal
      term_putc(char(b))
    else:
      break
  
  # Render frame after batch write
  if written > 0:
    term_render()
    
  return written

proc pty_read_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.} =
  ## Read from slave (input to shell). Gets master input.
  let pty = get_pty_from_fd(fd)
  if pty == nil: return -1
  
  var read_count = 0
  let buf = cast[ptr UncheckedArray[byte]](data)
  for i in 0 ..< len:
    let b = ring_pop(pty.master_to_slave, pty.mts_head, pty.mts_tail)
    if b < 0: break
    buf[i] = byte(b)
    read_count += 1
    
    # Echo if enabled
    if pty.echo and pty.mode == lmCanon:
      discard ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, byte(b))
      term_putc(char(b))
      
  if read_count > 0 and pty.echo:
    term_render()
    
  return read_count

proc pty_has_data_for_slave*(pty_id: int): bool {.exportc, cdecl.} =
  ## Check if there's input waiting for the slave.
  if pty_id < 0 or pty_id >= MAX_PTYS: return false
  if not ptys[pty_id].active: return false
  return ring_count(ptys[pty_id].mts_head, ptys[pty_id].mts_tail) > 0

proc pty_push_input*(pty_id: int, ch: char) {.exportc, cdecl.} =
  ## Push a character to the master-to-slave buffer (keyboard input).
  if pty_id < 0 or pty_id >= MAX_PTYS: return
  if not ptys[pty_id].active: return
  discard ring_push(ptys[pty_id].master_to_slave, 
                    ptys[pty_id].mts_head, 
                    ptys[pty_id].mts_tail, 
                    byte(ch))
