'use strict';

const { guessHandleType } = internalBinding('util');
exports.getMainThreadStdio = getMainThreadStdio;

function dummyDestroy(err, cb) {
  cb(err);

  // We need to emit 'close' anyway so that the closing
  // of the stream is observable. We just make sure we
  // are not going to do it twice.
  // The 'close' event is needed so that finished and
  // pipeline work correctly.
  if (!this._writableState.emitClose) {
    process.nextTick(() => {
      this.emit('close');
    });
  }
}

function getMainThreadStdio() {
  var stdin;
  var stdout;
  var stderr;

  function getStdout() {
    if (stdout) return stdout;
    stdout = createWritableStdioStream(1);
    stdout.destroySoon = stdout.destroy;
    // Override _destroy so that the fd is never actually closed.
    stdout._destroy = dummyDestroy;
    if (stdout.isTTY) {
      process.on('SIGWINCH', () => stdout._refreshSize());
    }
    return stdout;
  }

  function getStderr() {
    if (stderr) return stderr;
    stderr = createWritableStdioStream(2);
    stderr.destroySoon = stderr.destroy;
    // Override _destroy so that the fd is never actually closed.
    stderr._destroy = dummyDestroy;
    if (stderr.isTTY) {
      process.on('SIGWINCH', () => stderr._refreshSize());
    }
    return stderr;
  }

  function getStdin() {
    if (stdin) return stdin;
    const fd = 0;

    switch (guessHandleType(fd)) {
      case 'TTY':
        var tty = require('tty');
        stdin = new tty.ReadStream(fd, {
          highWaterMark: 0,
          readable: true,
          writable: false
        });
        break;

      case 'FILE':
        var fs = require('fs');
        stdin = new fs.ReadStream(null, { fd: fd, autoClose: false });
        break;

      case 'PIPE':
      case 'TCP':
        var net = require('net');

        // It could be that process has been started with an IPC channel
        // sitting on fd=0, in such case the pipe for this fd is already
        // present and creating a new one will lead to the assertion failure
        // in libuv.
        if (process.channel && process.channel.fd === fd) {
          stdin = new net.Socket({
            handle: process.channel,
            readable: true,
            writable: false,
            manualStart: true
          });
        } else {
          stdin = new net.Socket({
            fd: fd,
            readable: true,
            writable: false,
            manualStart: true
          });
        }
        // Make sure the stdin can't be `.end()`-ed
        stdin._writableState.ended = true;
        break;

      default:
        // Provide a dummy contentless input for e.g. non-console
        // Windows applications.
        const { Readable } = require('stream');
        stdin = new Readable({ read() {} });
        stdin.push(null);
    }

    // For supporting legacy API we put the FD here.
    stdin.fd = fd;

    // `stdin` starts out life in a paused state, but node doesn't
    // know yet. Explicitly to readStop() it to put it in the
    // not-reading state.
    if (stdin._handle && stdin._handle.readStop) {
      stdin._handle.reading = false;
      stdin._readableState.reading = false;
      stdin._handle.readStop();
    }

    // If the user calls stdin.pause(), then we need to stop reading
    // once the stream implementation does so (one nextTick later),
    // so that the process can close down.
    stdin.on('pause', () => {
      process.nextTick(onpause);
    });

    function onpause() {
      if (!stdin._handle)
        return;
      if (stdin._handle.reading && !stdin.readableFlowing) {
        stdin._readableState.reading = false;
        stdin._handle.reading = false;
        stdin._handle.readStop();
      }
    }

    return stdin;
  }

  exports.resetStdioForTesting = function() {
    stdin = undefined;
    stdout = undefined;
    stderr = undefined;
  };

  return {
    getStdout,
    getStderr,
    getStdin
  };
}

function createWritableStdioStream(fd) {
  var stream;
  // Note stream._type is used for test-module-load-list.js
  switch (guessHandleType(fd)) {
    case 'TTY':
      var tty = require('tty');
      stream = new tty.WriteStream(fd);
      stream._type = 'tty';
      break;

    case 'FILE':
      const SyncWriteStream = require('internal/fs/sync_write_stream');
      stream = new SyncWriteStream(fd, { autoClose: false });
      stream._type = 'fs';
      break;

    case 'PIPE':
    case 'TCP':
      var net = require('net');

      // If fd is already being used for the IPC channel, libuv will return
      // an error when trying to use it again. In that case, create the socket
      // using the existing handle instead of the fd.
      if (process.channel && process.channel.fd === fd) {
        stream = new net.Socket({
          handle: process.channel,
          readable: false,
          writable: true
        });
      } else {
        stream = new net.Socket({
          fd,
          readable: false,
          writable: true
        });
      }

      stream._type = 'pipe';
      break;

    default:
      // Provide a dummy black-hole output for e.g. non-console
      // Windows applications.
      const { Writable } = require('stream');
      stream = new Writable({
        write(buf, enc, cb) {
          cb();
        }
      });
  }

  // For supporting legacy API we put the FD here.
  stream.fd = fd;

  stream._isStdio = true;

  return stream;
}
