text: """Module for calculating random or non-random delay
  """
  from math import ceil, log, sqrt
  from random import uniform
  from typing import List, Union
  
  from numpy import arange
  from scipy.special import expit  # pylint: disable=no-name-in-module
  
  from jobfunnel.config import DelayConfig
  from jobfunnel.resources import DelayAlgorithm
  
  
  def _c_delay(list_len: int, delay: Union[int, float]):
      """Sets single delay value to whole list.
      """
      delays = [delay] * list_len
      # sets incrementing offsets to the first 8 elements
      inc = .2  # Increment set to .2
      offset = len(delays[0:8]) / 5  # offset
      # checks if delay is < 1.5
      if delay < 1.5:
          # changes increment and offset, to prevent 0s and negative nums
          inc = delay / 8
          offset = float(len(delays[0:8])) * inc
      # division here is faster since they are both ints
      delays[0:8] = [(x - offset) + i * inc for i, x in enumerate(delays[0:8])]
      return delays
  
  
  def _lin_delay(list_len: int, delay: Union[int, float]):
      """Calculates y=.2*x and sets y=delay at intersection of x between lines.
      """
      # calculates x value where lines intersect
      its = 5 * delay  # its = intersection
      # any delay of .2 or less is hard delay
      if its <= 1:
          return _c_delay(list_len, delay)
      else:
          # prevents slicing from breaking if delay is a float
          if isinstance(its, float):
              its = int(ceil(its))
          # create list of x values based on scrape list size
          delays = [*range(list_len)]
          delays[0:its] = [x / 5 for x in delays[0:its]]
          delays[its:] = [delay] * (len(delays) - its)
          return delays
  
  
  def _sig_delay(list_len: int, delay: Union[int, float]):
      """Calculates Richards/Sigmoid curve for delay.
      NOTE: https://en.wikipedia.org/wiki/Generalised_logistic_function
      """
      gr = sqrt(delay) * 4  # growth rate
      y_0 = log(4 * delay)  # Y(0)
      # calculates sigmoid curve using vars rewritten to be our x
      delays = delay * expit(arange(list_len) / gr - y_0)
      return delays.tolist()  # convert np array back to list
  
  
  def calculate_delays(list_len: int, delay_config: DelayConfig) -> List[float]:
      """Checks delay config and returns calculated delay list.
  
      NOTE: we do this to be respectful to online job sources
      TODO: we should be able to calculate delays on-demand.
  
      Args:
          list_len: length of scrape job list
          delay_config: Delay configuration dictionary
  
      Returns:
          list of delay time matching length of scrape job list
      """
      delay_config.validate()
  
      # Delay calculations using specified equations
      if delay_config.algorithm == DelayAlgorithm.CONSTANT:
          delay_vals = _c_delay(list_len, delay_config.max_duration)
      elif delay_config.algorithm == DelayAlgorithm.LINEAR:
          delay_vals = _lin_delay(list_len, delay_config.max_duration)
      elif delay_config.algorithm == DelayAlgorithm.SIGMOID:
          delay_vals = _sig_delay(list_len, delay_config.max_duration)
      else:
          raise ValueError(f"Cannot calculate delay for {delay_config.algorithm}")
  
      # Check if minimum delay is above 0 and less than last element
      if delay_config.min_duration > 0:
          # sets min_duration to values greater than itself in delay_vals
          for i, n in enumerate(delay_vals):
              if n > delay_config.min_duration:
                  break
              delay_vals[i] = delay_config.min_duration
  
      # Outputs final list of delays rounded up to 3 decimal places
      if delay_config.random:  # check if random delay was specified
          # random.uniform(a, b) a = lower bound, b = upper bound
          if delay_config.converge:  # checks if converging delay is True
              # delay_vals = lower bound, delay = upper bound
              durations = [
                  round(uniform(x, delay_config.max_duration), 3)
                  for x in delay_vals
              ]
          else:
              # lb = lower bounds, delay_vals = upper bound
              durations = [
                  round(uniform(delay_config.min_duration, x), 3)
                  for x in delay_vals
              ]
  
      else:
          durations = [round(i, 3) for i in delay_vals]
  
      # Always set first element to 0 so scrape starts right away
      durations[0] = 0.0
  
      return durations
  