export function throttle(delay, callback, options) {
  const { noTrailing = false, noLeading = false, debounceMode = undefined } = options || {}
  let timeoutID
  let cancelled = false
  let lastExec = 0
  function clearExistingTimeout() {
    if (timeoutID) {
      clearTimeout(timeoutID)
    }
  }
  function cancel(options) {
    const { upcomingOnly = false } = options || {}
    clearExistingTimeout()
    cancelled = !upcomingOnly
  }
  function wrapper(...arguments_) {
    let self = this
    let elapsed = Date.now() - lastExec
    if (cancelled) {
      return
    }
    function exec() {
      lastExec = Date.now()
      callback.apply(self, arguments_)
    }
    function clear() {
      timeoutID = undefined
    }
    if (!noLeading && debounceMode && !timeoutID) {
      exec()
    }
    clearExistingTimeout()
    if (debounceMode === undefined && elapsed > delay) {
      if (noLeading) {
        lastExec = Date.now()
        if (!noTrailing) {
          timeoutID = setTimeout(debounceMode ? clear : exec, delay)
        }
      } else {
        exec()
      }
    } else if (noTrailing !== true) {
      timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay)
    }
  }
  wrapper.cancel = cancel
  return wrapper
}

export function debounce(delay, callback, options) {
  const { atBegin = false } = options || {}
  return throttle(delay, callback, { debounceMode: atBegin !== false })
}