///|
/// A coarse classification of I/O errors, modeled after Rust's
/// `std::io::ErrorKind`.
pub(all) enum IOErrorKind {
  NotFound
  PermissionDenied
  AlreadyExists
  InvalidInput
  Interrupted
  BrokenPipe
  IsADirectory
  NotADirectory
  Unsupported
  Other
} derive(Eq, Debug)

///|
/// An I/O error, modeled after Rust's `std::io::Error`.
///
/// `code` is the errno number displayed for OS errors (the host's number on
/// native, a Linux-style number on wasm), or `-1` for errors that do not
/// originate from the operating system.
pub suberror IOError {
  IOError(kind~ : IOErrorKind, code~ : Int32, message~ : String)
} derive(Debug)

///|
/// Renders like Rust's `io::Error` `Display`:
/// `No such file or directory (os error 2)` for OS errors, or just the
/// message for other errors.
pub impl Show for IOError with fn output(self, logger) {
  let IOError(code~, message~, ..) = self
  logger.write_string(message)
  if code >= 0 {
    logger.write_string(" (os error ")
    logger.write_string(code.to_string())
    logger.write_string(")")
  }
}

///|
/// The kind of this error.
pub fn IOError::kind(self : IOError) -> IOErrorKind {
  let IOError(kind~, ..) = self
  kind
}

///|
/// The errno number of this error as displayed (see `IOError`), or `-1` if
/// it is not an OS error.
pub fn IOError::code(self : IOError) -> Int32 {
  let IOError(code~, ..) = self
  code
}

///|
/// The message of this error (without the ` (os error N)` suffix).
pub fn IOError::message(self : IOError) -> String {
  let IOError(message~, ..) = self
  message
}

///|
/// Builds an error from a Linux-style errno number. The kind is derived
/// from it, while the displayed code and message are the host's (e.g.
/// `ELOOP` is 62 on macOS), like Rust's `io::Error`.
pub fn IOError::from_errno(code : Int32) -> IOError {
  let (host, message) = host_error(code)
  IOError(
    kind=errno_kind(code),
    code=host,
    message=message.unwrap_or(strerror(code)),
  )
}

///|
/// Builds an error that does not originate from the operating system.
pub fn IOError::other(kind : IOErrorKind, message : String) -> IOError {
  IOError(kind~, code=-1, message~)
}

///|
/// Maps a Linux errno to an error kind (mirrors Rust's `decode_error_kind`).
fn errno_kind(code : Int32) -> IOErrorKind {
  match code {
    1 | 13 => PermissionDenied
    2 => NotFound
    17 => AlreadyExists
    22 => InvalidInput
    4 => Interrupted
    32 => BrokenPipe
    21 => IsADirectory
    20 => NotADirectory
    38 | 95 => Unsupported
    _ => Other
  }
}

///|
/// Linux (glibc) `strerror` texts.
fn strerror(code : Int32) -> String {
  match code {
    1 => "Operation not permitted"
    2 => "No such file or directory"
    3 => "No such process"
    4 => "Interrupted system call"
    5 => "Input/output error"
    6 => "No such device or address"
    7 => "Argument list too long"
    8 => "Exec format error"
    9 => "Bad file descriptor"
    10 => "No child processes"
    11 => "Resource temporarily unavailable"
    12 => "Cannot allocate memory"
    13 => "Permission denied"
    14 => "Bad address"
    15 => "Block device required"
    16 => "Device or resource busy"
    17 => "File exists"
    18 => "Invalid cross-device link"
    19 => "No such device"
    20 => "Not a directory"
    21 => "Is a directory"
    22 => "Invalid argument"
    23 => "Too many open files in system"
    24 => "Too many open files"
    25 => "Inappropriate ioctl for device"
    26 => "Text file busy"
    27 => "File too large"
    28 => "No space left on device"
    29 => "Illegal seek"
    30 => "Read-only file system"
    31 => "Too many links"
    32 => "Broken pipe"
    33 => "Numerical argument out of domain"
    34 => "Numerical result out of range"
    35 => "Resource deadlock avoided"
    36 => "File name too long"
    37 => "No locks available"
    38 => "Function not implemented"
    39 => "Directory not empty"
    40 => "Too many levels of symbolic links"
    42 => "No message of desired type"
    43 => "Identifier removed"
    61 => "No data available"
    62 => "Timer expired"
    67 => "Link has been severed"
    71 => "Protocol error"
    72 => "Multihop attempted"
    74 => "Bad message"
    75 => "Value too large for defined data type"
    84 => "Invalid or incomplete multibyte or wide character"
    88 => "Socket operation on non-socket"
    89 => "Destination address required"
    90 => "Message too long"
    91 => "Protocol wrong type for socket"
    92 => "Protocol not available"
    93 => "Protocol not supported"
    95 => "Operation not supported"
    97 => "Address family not supported by protocol"
    98 => "Address already in use"
    99 => "Cannot assign requested address"
    100 => "Network is down"
    101 => "Network is unreachable"
    102 => "Network dropped connection on reset"
    103 => "Software caused connection abort"
    104 => "Connection reset by peer"
    105 => "No buffer space available"
    106 => "Transport endpoint is already connected"
    107 => "Transport endpoint is not connected"
    110 => "Connection timed out"
    111 => "Connection refused"
    113 => "No route to host"
    114 => "Operation already in progress"
    115 => "Operation now in progress"
    116 => "Stale file handle"
    122 => "Disk quota exceeded"
    125 => "Operation canceled"
    130 => "Owner died"
    131 => "State not recoverable"
    _ => "Unknown error \{code}"
  }
}