// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Input channel type for `stdin`
pub struct Input {
  /// A builtin UTF-8 text view of the input channel,
  /// for text input convenience
  text : @io.Utf8Reader
  priv io : Result[@event_loop.IoHandle, Error]
  priv ctrl : @io.BinaryReaderController
}

///|
/// Return the file descriptor of an input channel.
///
/// If the initialization of the input channel failed,
/// for example when `stdin` does not exist (Windows) or is not a valid fd (Unix-like),
/// This method will fail with error.
/// So `.fd()` can be used as a way to tell if `stdin` is valid.
pub fn Input::fd(self : Input) -> @fd_util.Fd raise {
  self.io.unwrap_or_error().fd()
}

///|
pub impl @io.BinaryReader for Input

///|
impl @io.BinaryReaderImpl for Input with fn raw_read_impl(
  self,
  buf,
  offset~,
  max_len~,
) {
  self.io
  .unwrap_or_error()
  .read(
    buf,
    offset~,
    len=max_len,
    context="impl @io.BinaryReader for @stdio.Input",
  )
}

///|
impl @io.BinaryReaderImpl for Input with fn get_controller(self) {
  self.ctrl
}

///|
pub extend Input with @io.BinaryReader::{
  read_into,
  read,
  read_at_least,
  peek,
  peek_at_least,
  read_exactly,
  drop_exactly,
  peek_exactly,
  read_all,
  drop_all,
  read_until,
  peek_until,
}

///|
/// Output channel type for `stdout`/`stderr`
pub struct Output {
  /// A builtin UTF-8 text view of the output channel,
  /// for text output convenience
  text : @io.Utf8Writer
  priv io : Result[@event_loop.IoHandle, Error]
  priv ctrl : @io.BinaryWriterController
}

///|
/// Return the file descriptor of an output channel.
///
/// If the initialization of the output channel failed,
/// for example when `stdout` does not exist (Windows) or is not a valid fd (Unix-like),
/// This method will fail with error.
/// So `.fd()` can be used as a way to tell if `stdout`/`stderr` is valid.
pub fn Output::fd(self : Output) -> @fd_util.Fd raise {
  self.io.unwrap_or_error().fd()
}

///|
pub impl @io.BinaryWriter for Output

///|
impl @io.BinaryWriterImpl for Output with fn raw_write_impl(
  self,
  buf,
  offset~,
  len~,
) {
  self.io
  .unwrap_or_error()
  .write(buf, offset~, len~, context="impl @io.BinaryWriter for @stdio.Output")
}

///|
impl @io.BinaryWriterImpl for Output with fn get_controller(self) {
  self.ctrl
}

///|
pub extend Output with @io.BinaryWriter::{write, write_reader}

///|
/// The standard input channel.
///
/// If `stdin` does not exist or is invalid,
/// all operations on `stdin` will immediately fail with error.
///
/// Windows note: console handles are text based on Windows.
/// If `stdin` is a Windows console input,
/// unicode text read from the console will be decoded to UTF-8.
pub let stdin : Input = {
  let stdin : Input = {
    text: @io.Utf8Reader::new_uninitialized(),
    io: @event_loop.stdin,
    ctrl: BinaryReaderController(),
  }
  @event_loop.register_hook(init=() => stdin.text.set_inner(stdin), exit=() => {
    stdin.text.close()
  })
  stdin
}

///|
/// The standard output channel.
///
/// If `stdout` does not exist or is invalid,
/// all operations on `stdout` will immediately fail with error.
///
/// Windows note: console handles are text based on Windows.
/// If `stdout` is a Windows console output,
/// binary IO over `stdout` will be interpreted as UTF-8 sequence,
/// and submitted to the actual console screen buffer via unicode-safe API.
/// If the input is not valid UTF-8, the correct parts will be preserved,
/// but the final output for incorrect parts is undefined
pub let stdout : Output = {
  let stdout : Output = {
    text: @io.Utf8Writer::new_uninitialized(),
    io: @event_loop.stdout,
    ctrl: BinaryWriterController(),
  }
  @event_loop.register_hook(init=() => stdout.text.set_inner(stdout), exit=() => {
    stdout.text.close()
  })
  stdout
}

///|
/// The standard error channel.
///
/// If `stderr` does not exist or is invalid,
/// all operations on `stderr` will immediately fail with error.
///
/// Windows note: console handles are text based on Windows.
/// If `stderr` is a Windows console output,
/// binary IO over `stderr` will be interpreted as UTF-8 sequence,
/// and submitted to the actual console screen buffer via unicode-safe API.
/// If the input is not valid UTF-8, the correct parts will be preserved,
/// but the final output for incorrect parts is undefined
pub let stderr : Output = {
  let stderr : Output = {
    text: @io.Utf8Writer::new_uninitialized(),
    io: @event_loop.stderr,
    ctrl: BinaryWriterController(),
  }
  @event_loop.register_hook(init=() => stderr.text.set_inner(stderr), exit=() => {
    stderr.text.close()
  })
  stderr
}