// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of "common_patch.dart";

@patch
class _StdIOUtils {
  @patch
  static Stdin _getStdioInputStream(int fd) {
    final type = _getStdioHandleType(fd);
    if (type is OSError) {
      throw FileSystemException(
        "Failed to get type of stdio handle (fd $fd)",
        "",
        type,
      );
    }
    switch (type) {
      case _stdioHandleTypeTerminal:
      case _stdioHandleTypePipe:
      case _stdioHandleTypeSocket:
      case _stdioHandleTypeOther:
        return Stdin._(_Socket._readPipe(fd), fd);
      case _stdioHandleTypeFile:
        return Stdin._(_FileStream.forStdin(), fd);
    }
    throw UnsupportedError("Unexpected handle type $type");
  }

  @patch
  static _getStdioOutputStream(int fd) {
    final type = _getStdioHandleType(fd);
    if (type is OSError) {
      throw FileSystemException(
        "Failed to get type of stdio handle (fd $fd)",
        "",
        type,
      );
    }
    return Stdout._(IOSink(_StdConsumer._(fd)), fd);
  }

  @patch
  static int? _socketType(Socket socket) {
    if (socket is _Socket) return _nativeSocketType(socket._nativeSocket);
    return null;
  }

  static int _nativeSocketType(_NativeSocket nativeSocket) {
    if (nativeSocket.isClosed) {
      throw FileSystemException("Socket is closed");
    }
    if (nativeSocket.isClosing) {
      throw FileSystemException("Socket is being closed");
    }
    var result = _getSocketType(nativeSocket);
    if (result is OSError) {
      throw FileSystemException("Error retrieving socket type", "", result);
    }
    return result;
  }

  @patch
  @pragma("vm:external-name", "File_GetStdioHandleType")
  external static _getStdioHandleType(int fd);
}

@patch
class Stdin {
  @patch
  int readByteSync() {
    var result = _readByte(_fd);
    if (result is OSError) {
      throw StdinException("Error reading byte from stdin", result);
    }
    return result;
  }

  @patch
  bool get echoMode {
    var result = _echoMode(_fd);
    if (result is OSError) {
      throw StdinException("Error getting terminal echo mode", result);
    }
    return result;
  }

  @patch
  void set echoMode(bool enabled) {
    var result = _setEchoMode(_fd, enabled);
    if (result is OSError) {
      throw StdinException("Error setting terminal echo mode", result);
    }
  }

  @patch
  bool get echoNewlineMode {
    var result = _echoNewlineMode(_fd);
    if (result is OSError) {
      throw StdinException("Error getting terminal echo newline mode", result);
    }
    return result;
  }

  @patch
  void set echoNewlineMode(bool enabled) {
    var result = _setEchoNewlineMode(_fd, enabled);
    if (result is OSError) {
      throw StdinException("Error setting terminal echo newline mode", result);
    }
  }

  @patch
  bool get lineMode {
    var result = _lineMode(_fd);
    if (result is OSError) {
      throw StdinException("Error getting terminal line mode", result);
    }
    return result;
  }

  @patch
  void set lineMode(bool enabled) {
    var result = _setLineMode(_fd, enabled);
    if (result is OSError) {
      throw StdinException("Error setting terminal line mode", result);
    }
  }

  @patch
  bool get supportsAnsiEscapes {
    var result = _supportsAnsiEscapes(_fd);
    if (result is OSError) {
      throw StdinException("Error determining ANSI support", result);
    }
    return result;
  }

  @pragma("vm:external-name", "Stdin_GetEchoMode")
  external static _echoMode(int fd);
  @pragma("vm:external-name", "Stdin_SetEchoMode")
  external static _setEchoMode(int fd, bool enabled);
  @pragma("vm:external-name", "Stdin_GetEchoNewlineMode")
  external static _echoNewlineMode(int fd);
  @pragma("vm:external-name", "Stdin_SetEchoNewlineMode")
  external static _setEchoNewlineMode(int fd, bool enabled);
  @pragma("vm:external-name", "Stdin_GetLineMode")
  external static _lineMode(int fd);
  @pragma("vm:external-name", "Stdin_SetLineMode")
  external static _setLineMode(int fd, bool enabled);
  @pragma("vm:external-name", "Stdin_ReadByte")
  external static _readByte(int fd);
  @pragma("vm:external-name", "Stdin_AnsiSupported")
  external static _supportsAnsiEscapes(int fd);
}

@patch
class Stdout {
  @patch
  bool _hasTerminal(int fd) => _getTerminalSize(fd) is List;
  @patch
  int _terminalColumns(int fd) => _terminalSize(fd)[0];
  @patch
  int _terminalLines(int fd) => _terminalSize(fd)[1];

  static List _terminalSize(int fd) {
    var size = _getTerminalSize(fd);
    if (size is! List) {
      throw StdoutException("Could not get terminal size", size);
    }
    return size;
  }

  @pragma("vm:external-name", "Stdout_GetTerminalSize")
  external static _getTerminalSize(int fd);

  @patch
  static bool _supportsAnsiEscapes(int fd) {
    var result = _getAnsiSupported(fd);
    if (result is! bool) {
      throw StdoutException("Error determining ANSI support", result);
    }
    return result;
  }

  @pragma("vm:external-name", "Stdout_AnsiSupported")
  external static _getAnsiSupported(int fd);
}

@pragma("vm:external-name", "Socket_GetStdioHandle")
external bool _getStdioHandle(_NativeSocket socket, int num);
@pragma("vm:external-name", "Socket_GetType")
external _getSocketType(_NativeSocket nativeSocket);
