;; Phase 14 — String ports + eof-object

(deftest
  "eof-object"
  (deftest
    "eof-object is eof"
    (assert=
      true
      (eof-object? (eof-object))
      "eof-object? returns true for eof-object"))
  (deftest
    "non-eof values are not eof"
    (assert= false (eof-object? nil) "nil is not eof")
    (assert= false (eof-object? "") "string is not eof")
    (assert= false (eof-object? 0) "zero is not eof")
    (assert= false (eof-object? false) "false is not eof"))
  (deftest
    "type-of eof-object"
    (assert=
      "eof-object"
      (type-of (eof-object))
      "type-of eof-object is eof-object")))

(deftest
  "open-input-string"
  (deftest
    "creates input port"
    (let
      (p (open-input-string "hello"))
      (assert= true (port? p) "is a port")
      (assert= true (input-port? p) "is an input port")
      (assert= false (output-port? p) "is not an output port")))
  (deftest
    "type-of input port"
    (let
      (p (open-input-string "x"))
      (assert= "input-port" (type-of p) "type-of is input-port"))))

(deftest
  "open-output-string"
  (deftest
    "creates output port"
    (let
      (p (open-output-string))
      (assert= true (port? p) "is a port")
      (assert= true (output-port? p) "is an output port")
      (assert= false (input-port? p) "is not an input port")))
  (deftest
    "type-of output port"
    (let
      (p (open-output-string))
      (assert= "output-port" (type-of p) "type-of is output-port"))))

(deftest
  "read-char"
  (deftest
    "reads chars sequentially"
    (let
      (p (open-input-string "ab"))
      (let
        (c1 (read-char p))
        (assert= true (char? c1) "first result is char")
        (assert= 97 (char->integer c1) "first char is a"))))
  (deftest
    "reads second char"
    (let
      (p (open-input-string "ab"))
      (read-char p)
      (let
        (c2 (read-char p))
        (assert= true (char? c2) "second result is char")
        (assert= 98 (char->integer c2) "second char is b"))))
  (deftest
    "returns eof at end"
    (let
      (p (open-input-string "x"))
      (read-char p)
      (assert= true (eof-object? (read-char p)) "eof after last char")))
  (deftest
    "empty string yields eof immediately"
    (let
      (p (open-input-string ""))
      (assert= true (eof-object? (read-char p)) "eof from empty string"))))

(deftest
  "peek-char"
  (deftest
    "peeks without consuming"
    (let
      (p (open-input-string "x"))
      (let
        (c1 (peek-char p))
        (let
          (c2 (peek-char p))
          (assert=
            (char->integer c1)
            (char->integer c2)
            "peek twice gives same char")))))
  (deftest
    "peek then read"
    (let
      (p (open-input-string "z"))
      (let
        (peeked (peek-char p))
        (let
          (read (read-char p))
          (assert=
            (char->integer peeked)
            (char->integer read)
            "peek and read agree")))))
  (deftest
    "peek at end returns eof"
    (let
      (p (open-input-string ""))
      (assert= true (eof-object? (peek-char p)) "eof on empty peek"))))

(deftest
  "read-line"
  (deftest
    "reads a single line"
    (let
      (p (open-input-string "hello"))
      (assert= "hello" (read-line p) "reads whole string as line")))
  (deftest
    "reads line up to newline"
    (let
      (p (open-input-string "foo\nbar"))
      (assert= "foo" (read-line p) "first line is foo")))
  (deftest
    "reads second line"
    (let
      (p (open-input-string "foo\nbar"))
      (read-line p)
      (assert= "bar" (read-line p) "second line is bar")))
  (deftest
    "returns eof on empty port"
    (let
      (p (open-input-string ""))
      (assert= true (eof-object? (read-line p)) "eof on empty")))
  (deftest
    "returns eof after last line"
    (let
      (p (open-input-string "hi"))
      (read-line p)
      (assert= true (eof-object? (read-line p)) "eof after reading"))))

(deftest
  "write-char and get-output-string"
  (deftest
    "write single char"
    (let
      (p (open-output-string))
      (write-char (make-char 65) p)
      (assert= "A" (get-output-string p) "write char A")))
  (deftest
    "write multiple chars"
    (let
      (p (open-output-string))
      (write-char (make-char 72) p)
      (write-char (make-char 105) p)
      (assert= "Hi" (get-output-string p) "write Hi"))))

(deftest
  "write-string"
  (deftest
    "write a string to port"
    (let
      (p (open-output-string))
      (write-string "hello" p)
      (assert= "hello" (get-output-string p) "write-string result")))
  (deftest
    "multiple writes concatenate"
    (let
      (p (open-output-string))
      (write-string "foo" p)
      (write-string "bar" p)
      (assert= "foobar" (get-output-string p) "concatenated writes"))))

(deftest
  "get-output-string idempotent"
  (let
    (p (open-output-string))
    (write-string "test" p)
    (assert= "test" (get-output-string p) "first call")
    (assert= "test" (get-output-string p) "second call same result")))

(deftest
  "char-ready?"
  (deftest
    "ready when chars available"
    (let
      (p (open-input-string "x"))
      (assert= true (char-ready? p) "ready with content")))
  (deftest
    "not ready when empty"
    (let
      (p (open-input-string ""))
      (assert= false (char-ready? p) "not ready when empty"))))

(deftest
  "close-port"
  (deftest
    "close input port"
    (let
      (p (open-input-string "hello"))
      (close-port p)
      (assert= true (eof-object? (read-char p)) "read after close gives eof")))
  (deftest
    "close output port"
    (let
      (p (open-output-string))
      (write-string "ok" p)
      (close-port p)
      (assert= "ok" (get-output-string p) "output preserved after close"))))

(deftest
  "roundtrip string via ports"
  (let
    (in (open-input-string "abc"))
    (let
      (out (open-output-string))
      (do
        (let
          (c1 (read-char in))
          (when (not (eof-object? c1)) (write-char c1 out)))
        (let
          (c2 (read-char in))
          (when (not (eof-object? c2)) (write-char c2 out)))
        (let
          (c3 (read-char in))
          (when (not (eof-object? c3)) (write-char c3 out)))
        (assert= "abc" (get-output-string out) "roundtrip via ports")))))
