module BBLib
  # Used to keep any numeric number between a set of bounds.
  # Passing nil as min or max represents no bounds in that direction.
  # min and max are inclusive to the allowed bounds.
  def self.keep_between(num, min, max)
    num = num.to_f unless num.is_a?(Numeric)
    num = min if min && num < min
    num = max if max && num > max
    num
  end

  # Similar to keep between but when a number exceeds max or is less than min
  # it is looped to the min or max value respectively.
  def self.loop_between(num, min, max)
    num = num.to_f unless num.is_a?(Numeric)
    num = max if min && num < min
    num = min if max && num > max
    num
  end

  NUMBER_WORDS = {
    special: {
      0 => nil,
      1 => 'one',
      2 => 'two',
      3 => 'three',
      4 => 'four',
      5 => 'five',
      6 => 'six',
      7 => 'seven',
      8 => 'eight',
      9 => 'nine',
      10 => 'ten',
      11 => 'eleven',
      12 => 'twleve',
      13 => 'thirteen',
      14 => 'fourteen',
      15 => 'fifteen',
      16 => 'sixteen',
      17 => 'seventeen',
      18 => 'eighteen',
      19 => 'nineteen'
    },
    double_range: {
      2 => 'twenty',
      3 => 'thirty',
      4 => 'forty',
      5 => 'fifty',
      6 => 'sixty',
      7 => 'seventy',
      8 => 'eighty',
      9 => 'ninety'
    },
    ranges: [
      nil, 'thousand', 'million', 'billion', 'trillion', 'quadrillion',
      'quintillion', 'sextillion', 'septillion'
    ]
  }

  # TODO: Support floats eventually?
  def self.number_spelled_out(number, range = 0, include_and: true)
    number = number.to_i
    negative = number.negative?
    number = number * -1 if negative
    return 'zero' if number.zero?
    str = []
    three_digit = number > 999 ? number.to_s[-3..-1].to_i : number
    case three_digit
    when 1..19
      str << NUMBER_WORDS[:special][three_digit]
    when 20..99
      str << NUMBER_WORDS[:double_range][three_digit.to_s[-2].to_i]
      str << NUMBER_WORDS[:special][three_digit.to_s[-1].to_i]
    when 100..999
      str << NUMBER_WORDS[:special][three_digit.to_s[0].to_i]
      str << 'hundred'
      str << 'and' if include_and && !three_digit.to_s.end_with?('00')
      if three_digit.to_s[-2].to_i == 1
        str << NUMBER_WORDS[:special][three_digit.to_s[-2..-1].to_i]
      else
        str << NUMBER_WORDS[:double_range][three_digit.to_s[-2].to_i]
        str << NUMBER_WORDS[:special][three_digit.to_s[-1].to_i]
      end
    end
    str << NUMBER_WORDS[:ranges][range] unless str.compact.empty?
    (negative ? 'negative ' : '') +
    ((number.to_s.size > 3 ? "#{number_spelled_out(number.to_s[0..-4].to_i, range + 1)} " : '') +
    str.compact.join(' ')).gsub(/\s+/, ' ')
  end
end