const { promisify } = require('util')
const binding = require('bindings')('bindings.node')
const AbstractBinding = require('@serialport/binding-abstract')
const linuxList = require('@serialport/bindings/lib/linux-list')
const Poller = require('@serialport/bindings/lib/poller')
const unixRead = require('@serialport/bindings/lib/unix-read')
const unixWrite = require('@serialport/bindings/lib/unix-write')
const { wrapWithHiddenComName } = require('@serialport/bindings/lib/legacy')

const defaultBindingOptions = Object.freeze({
  vmin: 1,
  vtime: 0,
})

const asyncOpen = promisify(binding.open)
const asyncClose = promisify(binding.close)
const asyncUpdate = promisify(binding.update)
const asyncSet = promisify(binding.set)
const asyncGet = promisify(binding.get)
const asyncGetBaudRate = promisify(binding.getBaudRate)
const asyncDrain = promisify(binding.drain)
const asyncFlush = promisify(binding.flush)

/**
 * The linux binding layer
 */
class LinuxBinding extends AbstractBinding {
  static list() {
    return wrapWithHiddenComName(linuxList())
  }

  constructor(opt = {}) {
    super(opt)
    this.bindingOptions = { ...defaultBindingOptions, ...opt.bindingOptions }
    this.fd = null
    this.writeOperation = null
  }

  get isOpen() {
    return this.fd !== null
  }

  async open(path, options) {
    await super.open(path, options)
    this.openOptions = { ...this.bindingOptions, ...options }
    const fd = await asyncOpen(path, this.openOptions)
    this.fd = fd
    this.poller = new Poller(fd)
  }

  async close() {
    await super.close()
    const fd = this.fd
    this.poller.stop()
    this.poller.destroy()
    this.poller = null
    this.openOptions = null
    this.fd = null
    return asyncClose(fd)
  }

  async read(buffer, offset, length) {
    await super.read(buffer, offset, length)
    return unixRead({ binding: this, buffer, offset, length })
  }

  async write(buffer) {
    this.writeOperation = super.write(buffer).then(async () => {
      if (buffer.length === 0) {
        return
      }
      await unixWrite({ binding: this, buffer })
      this.writeOperation = null
    })
    return this.writeOperation
  }

  async update(options) {
    await super.update(options)
    return asyncUpdate(this.fd, options)
  }

  async set(options) {
    await super.set(options)
    return asyncSet(this.fd, options)
  }

  async get() {
    await super.get()
    return asyncGet(this.fd)
  }

  async getBaudRate() {
    await super.get()
    return asyncGetBaudRate(this.fd)
  }

  async drain() {
    await super.drain()
    await this.writeOperation
    return asyncDrain(this.fd)
  }

  async flush() {
    await super.flush()
    return asyncFlush(this.fd)
  }
}

module.exports = LinuxBinding
