#
#
#            Nim's Runtime Library
#        (c) Copyright 2016 Eugene Kabanov
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# This module implements Linux epoll().
import std/[deques, tables]
import stew/base10

{.push raises: [].}

type
  SelectorImpl[T] = object
    epollFd: cint
    sigFd: Opt[cint]
    pidFd: Opt[cint]
    fds: Table[int32, SelectorKey[T]]
    signals: Table[int32, SelectorKey[T]]
    processes: Table[int32, SelectorKey[T]]
    signalMask: Sigset
    virtualHoles: Deque[int32]
    virtualId: int32
    childrenExited: bool
    pendingEvents: Deque[ReadyKey]

  Selector*[T] = ref SelectorImpl[T]

  SelectEventImpl = object
    efd: cint

  SelectEvent* = ptr SelectEventImpl

proc getVirtualId[T](s: Selector[T]): SelectResult[int32] =
  if len(s.virtualHoles) > 0:
    ok(s.virtualHoles.popLast())
  else:
    if s.virtualId == low(int32):
      err(oserrno.EMFILE)
    else:
      dec(s.virtualId)
      ok(s.virtualId)

proc isVirtualId(ident: int32): bool =
  ident < 0'i32

proc toString(key: int32|cint|SocketHandle|int): string =
  let fdi32 = when key is int32: key else: int32(key)
  if isVirtualId(fdi32):
    if fdi32 == -1:
      "InvalidIdent"
    else:
      "V" & Base10.toString(uint32(-fdi32))
  else:
    Base10.toString(uint32(fdi32))

template addKey[T](s: Selector[T], key: int32, skey: SelectorKey[T]) =
  if s.fds.hasKeyOrPut(key, skey):
    raiseAssert "Descriptor [" & key.toString() &
                "] is already registered in the selector!"

template getKey[T](s: Selector[T], key: int32): SelectorKey[T] =
  let
    defaultKey = SelectorKey[T](ident: InvalidIdent)
    pkey = s.fds.getOrDefault(key, defaultKey)
  doAssert(pkey.ident != InvalidIdent,
           "Descriptor [" & key.toString() &
           "] is not registered in the selector!")
  pkey

template checkKey[T](s: Selector[T], key: int32): bool =
  s.fds.contains(key)

proc addSignal[T](s: Selector[T], signal: int, skey: SelectorKey[T]) =
  if s.signals.hasKeyOrPut(int32(signal), skey):
    raiseAssert "Signal [" & $signal & "] is already registered in the selector"

template addProcess[T](s: Selector[T], pid: int, skey: SelectorKey[T]) =
  if s.processes.hasKeyOrPut(int32(pid), skey):
    raiseAssert "Process [" & $pid & "] is already registered in the selector"

proc freeKey[T](s: Selector[T], key: int32) =
  s.fds.del(key)
  if isVirtualId(key):
    s.virtualHoles.addFirst(key)

proc freeSignal[T](s: Selector[T], ident: int32) =
  s.signals.del(ident)

proc freeProcess[T](s: Selector[T], ident: int32) =
  s.processes.del(ident)

proc new*(t: typedesc[Selector], T: typedesc): SelectResult[Selector[T]] =
  var nmask: Sigset
  if sigemptyset(nmask) < 0:
    return err(osLastError())
  let epollFd = epoll_create(chronosEventsCount)
  if epollFd < 0:
    return err(osLastError())
  let selector = Selector[T](
    epollFd: epollFd,
    fds: initTable[int32, SelectorKey[T]](chronosInitialSize),
    signalMask: nmask,
    virtualId: -1'i32, # Should start with -1, because `InvalidIdent` == -1
    childrenExited: false,
    virtualHoles: initDeque[int32](),
    pendingEvents: initDeque[ReadyKey]()
  )
  ok(selector)

proc close2*[T](s: Selector[T]): SelectResult[void] =
  s.fds.clear()
  s.signals.clear()
  s.processes.clear()
  s.virtualHoles.clear()
  s.virtualId = -1'i32
  if handleEintr(osdefs.close(s.epollFd)) != 0:
    err(osLastError())
  else:
    ok()

proc new*(t: typedesc[SelectEvent]): SelectResult[SelectEvent] =
  let eFd = eventfd(0, EFD_CLOEXEC or EFD_NONBLOCK)
  if eFd == -1:
    return err(osLastError())
  var res = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
  res.efd = eFd
  ok(res)

proc trigger2*(event: SelectEvent): SelectResult[void] =
  var data: uint64 = 1
  let res = handleEintr(osdefs.write(event.efd, addr data, sizeof(uint64)))
  if res == -1:
    err(osLastError())
  elif res != sizeof(uint64):
    err(oserrno.EINVAL)
  else:
    ok()

proc close2*(event: SelectEvent): SelectResult[void] =
  let evFd = event.efd
  deallocShared(cast[pointer](event))
  let res = handleEintr(osdefs.close(evFd))
  if res == -1:
    err(osLastError())
  else:
    ok()

proc init(t: typedesc[EpollEvent], fdi: cint, events: set[Event]): EpollEvent =
  var res = uint32(EPOLLRDHUP)
  if Event.Read in events: res = res or uint32(EPOLLIN)
  if Event.Write in events: res = res or uint32(EPOLLOUT)
  if Event.Oneshot in events: res = res or uint32(EPOLLONESHOT)
  # We need this double conversion of type because otherwise in x64 environment
  # negative cint could be converted to big uint64.
  EpollEvent(events: res, data: EpollData(u64: uint64(uint32(fdi))))

proc registerHandle2*[T](s: Selector[T], fd: cint, events: set[Event],
                         data: T): SelectResult[void] =
  let skey = SelectorKey[T](ident: fd, events: events, param: 0, data: data)

  s.addKey(fd, skey)

  if events != {}:
    let epollEvents = EpollEvent.init(fd, events)
    if epoll_ctl(s.epollFd, EPOLL_CTL_ADD, fd, unsafeAddr(epollEvents)) != 0:
      s.freeKey(fd)
      return err(osLastError())
  ok()

proc updateHandle2*[T](s: Selector[T], fd: cint,
                       events: set[Event]): SelectResult[void] =
  const EventsMask = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
                      Event.User, Event.Oneshot, Event.Error}
  s.fds.withValue(int32(fd), pkey):
    doAssert(pkey[].events * EventsMask == {},
             "Descriptor [" & fd.toString() & "] could not be updated!")
    if pkey[].events != events:
      let epollEvents = EpollEvent.init(fd, events)
      if pkey[].events == {}:
        if epoll_ctl(s.epollFd, EPOLL_CTL_ADD, fd,
                     unsafeAddr(epollEvents)) != 0:
          return err(osLastError())
      else:
        if events != {}:
          if epoll_ctl(s.epollFd, EPOLL_CTL_MOD, fd,
                       unsafeAddr(epollEvents)) != 0:
            return err(osLastError())
        else:
          if epoll_ctl(s.epollFd, EPOLL_CTL_DEL, fd,
                       unsafeAddr epollEvents) != 0:
            return err(osLastError())
      pkey.events = events
  do:
    raiseAssert "Descriptor [" & fd.toString() &
                "] is not registered in the selector!"
  ok()

proc blockSignal[T](s: Selector[T], signal: int): SelectResult[bool] =
  let isMember = sigismember(s.signalMask, cint(signal))
  if isMember < 0:
    err(osLastError())
  elif isMember > 0:
    ok(false)
  else:
    var omask, nmask: Sigset
    if sigemptyset(nmask) < 0:
      return err(osLastError())
    if sigemptyset(omask) < 0:
      return err(osLastError())
    if sigaddset(nmask, cint(signal)) < 0:
      return err(osLastError())
    ? blockSignals(nmask, omask)
    if sigaddset(s.signalMask, cint(signal)) < 0:
      # Try to restore previous state of signals mask
      let errorCode = osLastError()
      discard unblockSignals(nmask, omask)
      return err(errorCode)
    ok(true)

proc unblockSignal[T](s: Selector[T], signal: int): SelectResult[bool] =
  let isMember = sigismember(s.signalMask, cint(signal))
  if isMember < 0:
    err(osLastError())
  elif isMember == 0:
    ok(false)
  else:
    var omask, nmask: Sigset
    if sigemptyset(nmask) < 0:
      return err(osLastError())
    if sigemptyset(omask) < 0:
      return err(osLastError())
    if sigaddset(nmask, cint(signal)) < 0:
      return err(osLastError())
    ? unblockSignals(nmask, omask)
    if sigdelset(s.signalMask, cint(signal)) < 0:
      # Try to restore previous state of signals mask
      let errorCode = osLastError()
      discard blockSignals(nmask, omask)
      return err(errorCode)
    ok(true)

template checkSignal(signal: int) =
  doAssert((signal >= 0) and (signal <= int(high(int32))),
           "Invalid signal value [" & $signal & "]")

proc registerSignalEvent[T](s: Selector[T], signal: int,
                            events: set[Event], param: int,
                            data: T): SelectResult[cint] =
  checkSignal(signal)

  let
    fdi32 = ? s.getVirtualId()
    selectorKey = SelectorKey[T](ident: signal, events: events,
                                param: param, data: data)
    signalKey = SelectorKey[T](ident: fdi32, events: events,
                               param: param, data: data)

  s.addKey(fdi32, selectorKey)
  s.addSignal(signal, signalKey)

  let mres =
    block:
      let res = s.blockSignal(signal)
      if res.isErr():
        s.freeKey(fdi32)
        s.freeSignal(int32(signal))
        return err(res.error())
      res.get()

  if not(mres):
    raiseAssert "Signal [" & $signal & "] could have only one handler at " &
                "the same time!"

  if s.sigFd.isSome():
    let res = signalfd(s.sigFd.get(), s.signalMask,
                       SFD_NONBLOCK or SFD_CLOEXEC)
    if res == -1:
      let errorCode = osLastError()
      s.freeKey(fdi32)
      s.freeSignal(int32(signal))
      discard s.unblockSignal(signal)
      return err(errorCode)
  else:
    let sigFd = signalfd(-1, s.signalMask, SFD_NONBLOCK or SFD_CLOEXEC)
    if sigFd == -1:
      let errorCode = osLastError()
      s.freeKey(fdi32)
      s.freeSignal(int32(signal))
      discard s.unblockSignal(signal)
      return err(errorCode)

    let fdKey = SelectorKey[T](ident: sigFd, events: {Event.Signal})
    s.addKey(sigFd, fdKey)

    let event = EpollEvent.init(sigFd, {Event.Read})
    if epoll_ctl(s.epollFd, EPOLL_CTL_ADD, sigFd, unsafeAddr(event)) != 0:
      let errorCode = osLastError()
      s.freeKey(fdi32)
      s.freeSignal(int32(signal))
      s.freeKey(sigFd)
      discard s.unblockSignal(signal)
      discard handleEintr(osdefs.close(sigFd))
      return err(errorCode)

    s.sigFd = Opt.some(sigFd)

  ok(cint(fdi32))

proc registerSignal*[T](s: Selector[T], signal: int,
                       data: T): SelectResult[cint] =
  registerSignalEvent(s, signal, {Event.Signal}, 0, data)

proc registerTimer2*[T](s: Selector[T], timeout: int, oneshot: bool,
                        data: T): SelectResult[cint] =
  let timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC or TFD_NONBLOCK)
  if timerFd == -1:
    return err(osLastError())

  let
    fdi32 = int32(timerFd)
    (key, event) =
      if oneshot:
        (
          SelectorKey[T](ident: timerFd, events: {Event.Timer, Event.Oneshot},
                         param: 0, data: data),
          EpollEvent.init(timerFd, {Event.Read, Event.Oneshot})
        )
      else:
        (
          SelectorKey[T](ident: timerFd, events: {Event.Timer},
                         param: 0, data: data),
          EpollEvent.init(timerFd, {Event.Read})
        )
  var timeStruct =
    if oneshot:
      Itimerspec(
        it_interval: Timespec(tv_sec: osdefs.Time(0), tv_nsec: 0),
        it_value: Timespec(tv_sec: osdefs.Time(timeout div 1_000),
                           tv_nsec: (timeout %% 1000) * 1_000_000)
      )
    else:
      Itimerspec(
        it_interval: Timespec(tv_sec: osdefs.Time(timeout div 1_000),
                              tv_nsec: 0),
        it_value: Timespec(tv_sec: osdefs.Time(timeout div 1_000),
                           tv_nsec: 0),
      )

  s.addKey(fdi32, key)

  var oldTs = Itimerspec()
  if timerfd_settime(timerFd, cint(0), timeStruct, oldTs) != 0:
    let errorCode = osLastError()
    s.freeKey(fdi32)
    discard handleEintr(osdefs.close(timerFd))
    return err(errorCode)

  if epoll_ctl(s.epollFd, EPOLL_CTL_ADD, timerFd, unsafeAddr(event)) != 0:
    let errorCode = osLastError()
    s.freeKey(fdi32)
    discard handleEintr(osdefs.close(timerFd))
    return err(errorCode)

  ok(cint(fdi32))

proc registerEvent2*[T](s: Selector[T], ev: SelectEvent,
                        data: T): SelectResult[cint] =
  doAssert(not(isNil(ev)))
  let
    key = SelectorKey[T](ident: ev.efd, events: {Event.User},
                         param: 0, data: data)
    event = EpollEvent.init(ev.efd, {Event.Read})

  s.addKey(ev.efd, key)

  if epoll_ctl(s.epollFd, EPOLL_CTL_ADD, ev.efd, unsafeAddr(event)) != 0:
    s.freeKey(ev.efd)
    return err(osLastError())

  ok(ev.efd)

template checkPid(pid: int) =
  when sizeof(int) == 8:
    doAssert(pid >= 0 and pid <= int(high(uint32)),
             "Invalid process idientified (pid) value")
  else:
    doAssert(pid >= 0 and pid <= high(int32),
             "Invalid process idientified (pid) value")

proc registerProcess*[T](s: Selector, pid: int, data: T): SelectResult[cint] =
  checkPid(pid)

  let
    fdi32 = ? s.getVirtualId()
    events = {Event.Process, Event.Oneshot}
    selectorKey = SelectorKey[T](ident: pid, events: events, param: 0,
                                 data: data)
    processKey = SelectorKey[T](ident: fdi32, events: events, param: 0,
                                data: data)

  s.addProcess(pid, processKey)
  s.addKey(fdi32, selectorKey)

  if s.pidFd.isNone():
    let res = registerSignalEvent(s, int(SIGCHLD), {Event.Signal}, 0, data)
    if res.isErr():
      s.freeKey(fdi32)
      s.freeProcess(int32(pid))
      return err(res.error())
    s.pidFd = Opt.some(res.get())

  ok(cint(fdi32))

proc unregister2*[T](s: Selector[T], fd: cint): SelectResult[void] =
  let
    fdi32 = int32(fd)
    pkey = s.getKey(fdi32)

  if pkey.events != {}:
    if {Event.Read, Event.Write, Event.User} * pkey.events != {}:
      if epoll_ctl(s.epollFd, EPOLL_CTL_DEL, cint(pkey.ident), nil) != 0:
        return err(osLastError())

    elif Event.Timer in pkey.events:
      if Event.Finished notin pkey.events:
        if epoll_ctl(s.epollFd, EPOLL_CTL_DEL, fd, nil) != 0:
          let errorCode = osLastError()
          discard handleEintr(osdefs.close(fd))
          return err(errorCode)
      if handleEintr(osdefs.close(fd)) == -1:
        return err(osLastError())

    elif Event.Signal in pkey.events:
      if not(s.signals.hasKey(int32(pkey.ident))):
        raiseAssert "Signal " & pkey.ident.toString() &
                    " is not registered in the selector!"
      let sigFd =
        block:
          doAssert(s.sigFd.isSome(), "signalfd descriptor is missing")
          s.sigFd.get()

      s.freeSignal(int32(pkey.ident))

      if len(s.signals) > 0:
        let res = signalfd(sigFd, s.signalMask, SFD_NONBLOCK or SFD_CLOEXEC)
        if res == -1:
          let errorCode = osLastError()
          discard s.unblockSignal(pkey.ident)
          return err(errorCode)
      else:
        s.freeKey(sigFd)
        s.sigFd = Opt.none(cint)

        if epoll_ctl(s.epollFd, EPOLL_CTL_DEL, sigFd, nil) != 0:
          let errorCode = osLastError()
          discard handleEintr(osdefs.close(sigFd))
          discard s.unblockSignal(pkey.ident)
          return err(errorCode)

        if handleEintr(osdefs.close(sigFd)) != 0:
          let errorCode = osLastError()
          discard s.unblockSignal(pkey.ident)
          return err(errorCode)

      let mres = ? s.unblockSignal(pkey.ident)
      doAssert(mres, "Signal is not present in stored mask!")

    elif Event.Process in pkey.events:
      if not(s.processes.hasKey(int32(pkey.ident))):
        raiseAssert "Process " & pkey.ident.toString() &
                    " is not registered in the selector!"

      let pidFd =
        block:
          doAssert(s.pidFd.isSome(), "process descriptor is missing")
          s.pidFd.get()

      s.freeProcess(int32(pkey.ident))

      # We need to filter pending events queue for just unregistered process.
      if len(s.pendingEvents) > 0:
        s.pendingEvents =
          block:
            var res = initDeque[ReadyKey](len(s.pendingEvents))
            for item in s.pendingEvents.items():
              if item.fd != fdi32:
                res.addLast(item)
            res

      if len(s.processes) == 0:
        s.pidFd = Opt.none(cint)
        let res = s.unregister2(pidFd)
        if res.isErr():
          return err(res.error())

  s.freeKey(fdi32)
  ok()

proc unregister2*[T](s: Selector[T], event: SelectEvent): SelectResult[void] =
  s.unregister2(event.efd)

proc prepareKey[T](s: Selector[T], event: EpollEvent): Opt[ReadyKey] =
  let
    defaultKey = SelectorKey[T](ident: InvalidIdent)
    fdi32 =
      block:
        doAssert(event.data.u64 <= uint64(high(uint32)),
                 "Invalid user data value in epoll event object")
        cast[int32](event.data.u64)

  var
    pkey = s.getKey(fdi32)
    rkey = ReadyKey(fd: fdi32)

  if (event.events and EPOLLERR) != 0:
    rkey.events.incl(Event.Error)
    rkey.errorCode = oserrno.ECONNRESET

  if (event.events and EPOLLHUP) != 0 or (event.events and EPOLLRDHUP) != 0:
    rkey.events.incl(Event.Error)
    rkey.errorCode = oserrno.ECONNRESET

  if (event.events and EPOLLOUT) != 0:
    rkey.events.incl(Event.Write)

  if (event.events and EPOLLIN) != 0:
    if Event.Read in pkey.events:
      rkey.events.incl(Event.Read)

    elif Event.Timer in pkey.events:
      var data: uint64
      rkey.events.incl(Event.Timer)
      let res = handleEintr(osdefs.read(fdi32, addr data, sizeof(uint64)))
      if res != sizeof(uint64):
        rkey.events.incl(Event.Error)
        rkey.errorCode = osLastError()

    elif Event.Signal in pkey.events:
      var data: SignalFdInfo
      let res = handleEintr(osdefs.read(fdi32, addr data, sizeof(SignalFdInfo)))
      if res != sizeof(SignalFdInfo):
        # We could not obtain `signal` number so we can't report an error to
        # proper handler.
        return Opt.none(ReadyKey)
      if data.ssi_signo != uint32(SIGCHLD) or len(s.processes) == 0:
        let skey = s.signals.getOrDefault(cast[int32](data.ssi_signo),
                                          defaultKey)
        if skey.ident == InvalidIdent:
          # We do not have any handlers for received event so we can't report
          # an error to proper handler.
          return Opt.none(ReadyKey)
        rkey.events.incl(Event.Signal)
        rkey.fd = skey.ident
      else:
        # Indicate that SIGCHLD has been seen.
        s.childrenExited = true
        # Current signal processing.
        let pidKey = s.processes.getOrDefault(cast[int32](data.ssi_pid),
                                              defaultKey)
        if pidKey.ident == InvalidIdent:
          # We do not have any handlers with signal's pid.
          return Opt.none(ReadyKey)
        rkey.events.incl({Event.Process, Event.Oneshot, Event.Finished})
        rkey.fd = pidKey.ident
        # Mark process descriptor inside fds table as finished.
        var fdKey = s.fds.getOrDefault(int32(pidKey.ident), defaultKey)
        if fdKey.ident != InvalidIdent:
          fdKey.events.incl(Event.Finished)
          s.fds[int32(pidKey.ident)] = fdKey

    elif Event.User in pkey.events:
      var data: uint64
      let res = handleEintr(osdefs.read(fdi32, addr data, sizeof(uint64)))
      if res != sizeof(uint64):
        let errorCode = osLastError()
        case errorCode
        of oserrno.EAGAIN:
          return Opt.none(ReadyKey)
        else:
          rkey.events.incl({Event.User, Event.Error})
          rkey.errorCode = errorCode
      else:
        rkey.events.incl(Event.User)

  if Event.Oneshot in rkey.events:
    if Event.Timer in rkey.events:
      if epoll_ctl(s.epollFd, EPOLL_CTL_DEL, fdi32, nil) != 0:
        rkey.events.incl(Event.Error)
        rkey.errorCode = osLastError()
      # we are marking key with `Finished` event, to avoid double decrease.
      rkey.events.incl(Event.Finished)
      pkey.events.incl(Event.Finished)
      s.fds[fdi32] = pkey

  ok(rkey)

proc checkProcesses[T](s: Selector[T]) =
  # If SIGCHLD has been seen we need to check all processes we are monitoring
  # for completion, because in Linux SIGCHLD could be masked.
  # You can get more information in article "Signalfd is useless" -
  # https://ldpreload.com/blog/signalfd-is-useless?reposted-on-request
  if not(s.childrenExited):
    return

  let
    defaultKey = SelectorKey[T](ident: InvalidIdent)
    flags = WNOHANG or WNOWAIT or WSTOPPED or WEXITED
  s.childrenExited = false
  for pid, pidKey in s.processes.pairs():
    var fdKey = s.fds.getOrDefault(int32(pidKey.ident), defaultKey)
    if fdKey.ident != InvalidIdent:
      if Event.Finished notin fdKey.events:
        var sigInfo = SigInfo()
        let res = handleEintr(osdefs.waitid(P_PID, cast[Id](pid),
                                            sigInfo, flags))
        if (res == 0) and (cint(sigInfo.si_pid) == cint(pid)):
          fdKey.events.incl(Event.Finished)
          let rkey = ReadyKey(fd: pidKey.ident, events: fdKey.events)
          s.pendingEvents.addLast(rkey)
          s.fds[int32(pidKey.ident)] = fdKey

proc selectInto2*[T](s: Selector[T], timeout: int,
                     readyKeys: var openArray[ReadyKey]
                    ): SelectResult[int] =
  var
    queueEvents: array[chronosEventsCount, EpollEvent]
    k: int = 0

  verifySelectParams(timeout, -1, int(high(cint)))

  let
    maxEventsCount = min(len(queueEvents), len(readyKeys))
    maxPendingEventsCount = min(maxEventsCount, len(s.pendingEvents))
    maxNewEventsCount = max(maxEventsCount - maxPendingEventsCount, 0)

  let
    eventsCount =
      if maxNewEventsCount > 0:
        let res = handleEintr(epoll_wait(s.epollFd, addr(queueEvents[0]),
                                         cint(maxNewEventsCount),
                                         cint(timeout)))
        if res < 0:
          return err(osLastError())
        res
      else:
        0

  s.childrenExited = false

  for i in 0 ..< eventsCount:
    let rkey = s.prepareKey(queueEvents[i]).valueOr: continue
    readyKeys[k] = rkey
    inc(k)

  s.checkProcesses()

  let pendingEventsCount = min(len(readyKeys) - eventsCount,
                               len(s.pendingEvents))

  for i in 0 ..< pendingEventsCount:
    readyKeys[k] = s.pendingEvents.popFirst()
    inc(k)

  ok(k)

proc select2*[T](s: Selector[T], timeout: int): SelectResult[seq[ReadyKey]] =
  var res = newSeq[ReadyKey](chronosEventsCount)
  let count = ? selectInto2(s, timeout, res)
  res.setLen(count)
  ok(res)

proc newSelector*[T](): Selector[T] {.
     raises: [OSError].} =
  let res = Selector.new(T)
  if res.isErr(): raiseOSError(res.error())
  res.get()

proc close*[T](s: Selector[T]) {.
     raises: [IOSelectorsException].} =
  let res = s.close2()
  if res.isErr(): raiseIOSelectorsError(res.error())

proc newSelectEvent*(): SelectEvent {.
     raises: [IOSelectorsException].} =
  let res = SelectEvent.new()
  if res.isErr(): raiseIOSelectorsError(res.error())
  res.get()

proc trigger*(event: SelectEvent) {.
     raises: [IOSelectorsException].} =
  let res = event.trigger2()
  if res.isErr(): raiseIOSelectorsError(res.error())

proc close*(event: SelectEvent) {.
     raises: [IOSelectorsException].} =
  let res = event.close2()
  if res.isErr(): raiseIOSelectorsError(res.error())

proc registerHandle*[T](s: Selector[T], fd: cint | SocketHandle,
                        events: set[Event], data: T) {.
    raises: [IOSelectorsException].} =
  let res = registerHandle2(s, fd, events, data)
  if res.isErr(): raiseIOSelectorsError(res.error())

proc updateHandle*[T](s: Selector[T], fd: cint | SocketHandle,
                      events: set[Event]) {.
    raises: [IOSelectorsException].} =
  let res = updateHandle2(s, fd, events)
  if res.isErr(): raiseIOSelectorsError(res.error())

proc unregister*[T](s: Selector[T], fd: cint | SocketHandle) {.
     raises: [IOSelectorsException].} =
  let res = unregister2(s, fd)
  if res.isErr(): raiseIOSelectorsError(res.error())

proc unregister*[T](s: Selector[T], event: SelectEvent) {.
    raises: [IOSelectorsException].} =
  let res = unregister2(s, event)
  if res.isErr(): raiseIOSelectorsError(res.error())

proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
                       data: T): cint {.
    discardable, raises: [IOSelectorsException].} =
  let res = registerTimer2(s, timeout, oneshot, data)
  if res.isErr(): raiseIOSelectorsError(res.error())
  res.get()

proc registerEvent*[T](s: Selector[T], event: SelectEvent,
                       data: T) {.
     raises: [IOSelectorsException].} =
  let res = registerEvent2(s, event, data)
  if res.isErr(): raiseIOSelectorsError(res.error())

proc selectInto*[T](s: Selector[T], timeout: int,
                    readyKeys: var openArray[ReadyKey]): int {.
     raises: [IOSelectorsException].} =
  let res = selectInto2(s, timeout, readyKeys)
  if res.isErr(): raiseIOSelectorsError(res.error())
  res.get()

proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] =
  let res = select2(s, timeout)
  if res.isErr(): raiseIOSelectorsError(res.error())
  res.get()

proc contains*[T](s: Selector[T], fd: SocketHandle|cint): bool {.inline.} =
  s.checkKey(int32(fd))

proc setData*[T](s: Selector[T], fd: SocketHandle|cint, data: T): bool =
  s.fds.withValue(int32(fd), skey):
    skey[].data = data
    return true
  do:
    return false

template withData*[T](s: Selector[T], fd: SocketHandle|cint, value,
                      body: untyped) =
  s.fds.withValue(int32(fd), skey):
    var value = addr(skey[].data)
    body

template withData*[T](s: Selector[T], fd: SocketHandle|cint, value, body1,
                      body2: untyped) =
  s.fds.withValue(int32(fd), skey):
    var value = addr(skey[].data)
    body1
  do:
    body2

proc getFd*[T](s: Selector[T]): cint = s.epollFd
