require 'bigdecimal'
require_relative './humanize/locales'

module Humanize
  SPACE = ' '.freeze
  EMPTY = ''.freeze
  # Big numbers are big: http://wiki.answers.com/Q/What_number_is_after_vigintillion&src=ansTT

  def humanize(locale: Humanize.config.default_locale,
               decimals_as: Humanize.config.decimals_as)
    locale_class, spacer = Humanize.for_locale(locale)

    return locale_class::SUB_ONE_GROUPING[0] if zero?

    infinity = to_f.infinite?
    if infinity
      infinity_word = locale_class::INFINITY
      return infinity == 1 ? infinity_word : "#{locale_class::NEGATIVE}#{spacer}#{infinity_word}"
    elsif is_a?(Float) && nan?
      return locale_class::UNDEFINED
    end

    sign = locale_class::NEGATIVE if negative?

    parts = locale_class.new.humanize(abs)
    process_decimals(locale_class, locale, parts, decimals_as, spacer)
    Humanize.stringify(parts, sign, spacer)
  end

  def self.for_locale(locale)
    case locale.to_sym
    # NOTE: add locales here in ealphabetical order
    when :az, :de, :en, :es, :fr, :id, :ms, :pt, :ru, :vi
      [Object.const_get("Humanize::#{locale.capitalize}"), SPACE]
    when :th
      [Humanize::Th, EMPTY]
    when :tr
      [Humanize::Tr, SPACE]
    when :jp
      [Humanize::Jp, EMPTY]
    else
      raise "Unsupported humanize locale: #{locale}"
    end
  end

  def self.stringify(parts, sign, spacer)
    output = parts.reverse.join(spacer).squeeze(spacer)
    if locale_is?(:es) && sign
      "#{output}#{spacer}#{sign}"
    elsif sign
      "#{sign}#{spacer}#{output}"
    else
      output
    end
  end

  def self.locale_is?(locale)
    Humanize.config.default_locale == locale
  end

  def process_decimals(locale_class, locale, parts, decimals_as, spacer)
    return unless is_a?(Float) || is_a?(BigDecimal)

    # Why 15?
    # (byebug) BigDecimal.new(number, 15)
    # 0.8000015e1
    # (byebug) BigDecimal.new(number, 16)
    # 0.8000014999999999e1
    decimal = BigDecimal(self, 15) - BigDecimal(to_i)

    _sign, significant_digits, _base, exponent = decimal.split
    return if significant_digits == "0"

    grouping = locale_class::SUB_ONE_GROUPING
    leading_zeroes = [grouping[0]] * exponent.abs
    decimals_as = :digits if leading_zeroes.any?

    decimals_as_words =
      case decimals_as
      when :digits
        digits = significant_digits.chars.map do |num|
          grouping[num.to_i]
        end

        (leading_zeroes + digits).join(spacer)
      when :number
        significant_digits.to_i.humanize(locale: locale)
      end

    parts.insert(0, decimals_as_words, locale_class::POINT)
  end

  class << self
    attr_writer :config
  end

  def self.config
    @config ||= Configuration.new
  end

  def self.reset_config
    @config = Configuration.new
  end

  def self.configure
    yield(config)
  end

  class Configuration
    attr_accessor :default_locale, :decimals_as

    def initialize
      @default_locale = :en
      @decimals_as = :digits
    end
  end
end

class Integer
  include Humanize
end

class Float
  include Humanize
end

class BigDecimal
  include Humanize
end
