# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # This cop enforces consistency when using exponential notation
      # for numbers in the code (eg 1.2e4). Different styles are supported:
      # * `scientific` which enforces a mantissa between 1 (inclusive)
      #                and 10 (exclusive).
      # * `engineering` which enforces the exponent to be a multiple of 3
      #                 and the mantissa to be between 0.1 (inclusive)
      #                 and 10 (exclusive).
      # * `integral` which enforces the mantissa to always be a whole number
      #              without trailing zeroes.
      #
      # @example EnforcedStyle: scientific (default)
      #   # Enforces a mantissa between 1 (inclusive) and 10 (exclusive).
      #
      #   # bad
      #   10e6
      #   0.3e4
      #   11.7e5
      #   3.14e0
      #
      #   # good
      #   1e7
      #   3e3
      #   1.17e6
      #   3.14
      #
      # @example EnforcedStyle: engineering
      #   # Enforces using multiple of 3 exponents,
      #   # mantissa should be between 0.1 (inclusive) and 1000 (exclusive)
      #
      #   # bad
      #   3.2e7
      #   0.1e5
      #   12e5
      #   1232e6
      #
      #   # good
      #   32e6
      #   10e3
      #   1.2e6
      #   1.232e9
      #
      # @example EnforcedStyle: integral
      #   # Enforces the mantissa to have no decimal part and no
      #   # trailing zeroes.
      #
      #   # bad
      #   3.2e7
      #   0.1e5
      #   120e4
      #
      #   # good
      #   32e6
      #   1e4
      #   12e5
      #
      class ExponentialNotation < Base
        include ConfigurableEnforcedStyle
        MESSAGES = {
          scientific: 'Use a mantissa in [1, 10[.',
          engineering: 'Use an exponent divisible by 3 and a mantissa in [0.1, 1000[.',
          integral: 'Use an integer as mantissa, without trailing zero.'
        }.freeze

        def on_float(node)
          add_offense(node) if offense?(node)
        end

        private

        def scientific?(node)
          mantissa, = node.source.split('e')
          /^-?[1-9](\.\d*[0-9])?$/.match?(mantissa)
        end

        def engineering?(node)
          mantissa, exponent = node.source.split('e')
          return false unless /^-?\d+$/.match?(exponent)
          return false unless (exponent.to_i % 3).zero?
          return false if /^-?\d{4}/.match?(mantissa)
          return false if /^-?0\d/.match?(mantissa)
          return false if /^-?0.0/.match?(mantissa)

          true
        end

        def integral(node)
          mantissa, = node.source.split('e')
          /^-?[1-9](\d*[1-9])?$/.match?(mantissa)
        end

        def offense?(node)
          return false unless node.source['e']

          case style
          when :scientific
            !scientific?(node)
          when :engineering
            !engineering?(node)
          when :integral
            !integral(node)
          else
            false
          end
        end

        def message(_node)
          MESSAGES[style]
        end
      end
    end
  end
end
