# Wrappers around socket primitives that take a 'local-network' object and are
# thus easier to test.
#
# The current semantics of fake port-connections don't match UNIX socket ones,
# but we'll improve them as we learn more.

container local-network [
  data:&:@:port-connection
]

# Port connections represent connections to ports on localhost.
# Before passing a local-network object to network functions
# `start-reading-socket` and `start-writing-socket`, add port-connections to
# the local-network.
#
# For reading, `transmit-from-socket` will check for a
# port-connection on the port parameter that's been passed in. If there's
# no port-connectin for that port, it will return nothing and log. If
# there is a port-connection for that port, it will transmit the contents
# to the passed in sink.
#
# For writing, `start-writing-socket` returns a sink connecting the
# caller to the socket on the passed-in port.
container port-connection [
  port:num
  contents:text
]

def new-port-connection port:num, contents:text -> p:&:port-connection [
  local-scope
  load-ingredients
  p:&:port-connection <- new port-connection:type
  *p <- merge port, contents
]

def new-fake-network -> n:&:local-network [
  local-scope
  load-ingredients
  n:&:local-network <- new local-network:type
  local-network-ports:&:@:port-connection <- new port-connection:type, 0
  *n <- put *n, data:offset, local-network-ports
]

scenario write-to-fake-socket [
  local-scope
  single-port-network:&:local-network <- new-fake-network
  sink:&:sink:char, writer:num/routine <- start-writing-socket single-port-network, 8080
  sink <- write sink, 120/x
  close sink
  wait-for-routine writer
  tested-port-connections:&:@:port-connection <- get *single-port-network, data:offset
  tested-port-connection:port-connection <- index *tested-port-connections, 0
  contents:text <- get tested-port-connection, contents:offset
  10:@:char/raw <- copy *contents
  memory-should-contain [
    10:array:character <- [x]
  ]
]

def start-writing-socket network:&:local-network, port:num -> sink:&:sink:char, routine-id:num [
  local-scope
  load-ingredients
  source:&:source:char, sink:&:sink:char <- new-channel 30
  {
    break-if network
    socket:num <- $open-server-socket port
    session:num <- $accept socket
    # TODO Create channel implementation of write-to-socket.
    return sink, 0/routine-id
  }
  # fake network
  routine-id <- start-running transmit-to-fake-socket network, port, source
]

def transmit-to-fake-socket network:&:local-network, port:num, source:&:source:char -> network:&:local-network, source:&:source:char [
  local-scope
  load-ingredients
  # compute new port connection contents
  buf:&:buffer <- new-buffer 30
  {
    c:char, done?:bool, source <- read source
    break-unless c
    buf <- append buf, c
    break-if done?
    loop
  }
  contents:text <- buffer-to-array buf
  new-port-connection:&:port-connection <- new-port-connection port, contents
  # Got the contents of the channel, time to write to fake port.
  i:num <- copy 0
  port-connections:&:@:port-connection <- get *network, data:offset
  len:num <- length *port-connections
  {
    done?:bool <- greater-or-equal i, len
    break-if done?
    current:port-connection <- index *port-connections, i
    current-port:num <- get current, port:offset
    ports-match?:bool <- equal current-port, port
    i <- add i, 1
    loop-unless ports-match?
    # Found an existing connection on this port, overwrite.
    put-index *port-connections, i, *new-port-connection
    reply
  }
  # Couldn't find an existing connection on this port, initialize a new one.
  new-len:num <- add len, 1
  new-port-connections:&:@:port-connection <- new port-connection:type, new-len
  put *network, data:offset, new-port-connections
  i:num <- copy 0
  {
    done?:bool <- greater-or-equal i, len
    break-if done?
    tmp:port-connection <- index *port-connections, i
    put-index *new-port-connections, i, tmp
  }
  put-index *new-port-connections, len, *new-port-connection
]