const fs = require('fs')
const debug = require('debug')
const logger = debug('serialport/bindings/unixRead')
const { promisify } = require('util')

const readAsync = promisify(fs.read)

const readable = binding => {
  return new Promise((resolve, reject) => {
    binding.poller.once('readable', err => (err ? reject(err) : resolve()))
  })
}

const unixRead = async ({ binding, buffer, offset, length, fsReadAsync = readAsync }) => {
  logger('Starting read')
  if (!binding.isOpen) {
    const err = new Error('Port is not open')
    err.canceled = true
    throw err
  }

  try {
    const { bytesRead } = await fsReadAsync(binding.fd, buffer, offset, length, null)
    if (bytesRead === 0) {
      return unixRead({ binding, buffer, offset, length, fsReadAsync })
    }
    logger('Finished read', bytesRead, 'bytes')
    return { bytesRead, buffer }
  } catch (err) {
    logger('read error', err)
    if (err.code === 'EAGAIN' || err.code === 'EWOULDBLOCK' || err.code === 'EINTR') {
      if (!binding.isOpen) {
        const err = new Error('Port is not open')
        err.canceled = true
        throw err
      }
      logger('waiting for readable because of code:', err.code)
      await readable(binding)
      return unixRead({ binding, buffer, offset, length, fsReadAsync })
    }

    const disconnectError =
      err.code === 'EBADF' || // Bad file number means we got closed
      err.code === 'ENXIO' || // No such device or address probably usb disconnect
      err.code === 'UNKNOWN' ||
      err.errno === -1 // generic error

    if (disconnectError) {
      err.canceled = true
      logger('disconnecting', err)
    }

    throw err
  }
}

module.exports = unixRead
