module Spree
  module LocalizedNumber
    # This method overwrites the attribute setters of a model
    # to make them use the LocalizedNumber parsing method.
    # It works with ActiveRecord "normal" attributes
    # and Preference attributes.
    # It also adds a validation on the input format.
    # It accepts as arguments a variable number of attribute as symbols
    def localize_number(*attributes)
      validate :validate_localizable_number

      attributes.each do |attribute|
        setter = "#{attribute}="
        old_setter = instance_method(setter) if table_exists? && !column_names.include?(attribute.to_s)

        define_method(setter) do |number|
          if Spree::Config.enable_localized_number? && Spree::LocalizedNumber.valid_localizable_number?(number)
            number = Spree::LocalizedNumber.parse(number)
          elsif Spree::Config.enable_localized_number?
            @invalid_localized_number ||= []
            @invalid_localized_number << attribute
            number = nil
          end
          if has_attribute?(attribute)
            self[attribute] = number
          else
            old_setter.bind(self).call(number)
          end
        end
      end

      define_method(:validate_localizable_number) do
        return unless Spree::Config.enable_localized_number?

        @invalid_localized_number.andand.each do |error_attribute|
          errors.set(error_attribute, [I18n.t('spree.localized_number.invalid_format')])
        end
      end
    end

    def self.valid_localizable_number?(number)
      return true unless number.is_a?(String) || number.respond_to?(:to_d)
      return false if number =~ /[\.,]\d{2}[\.,]/

      true
    end

    def self.parse(number)
      return nil if number.blank?
      return number.to_d unless number.is_a?(String)

      number = number.gsub(/[^\d.,-]/, '') # Replace all Currency Symbols, Letters and -- from the string

      add_trailing_zeros(number)

      number = number.gsub(/[\.,]/, '')    # Replace all (.) and (,) so the string result becomes in "cents"
      number.to_d / 100                    # Let to_decimal do the rest
    end

    def self.add_trailing_zeros(number)
      # If string ends in a single digit (e.g. ,2), make it ,20 in order for the result to be in "cents"
      number << "0" if number =~ /^.*[\.,]\d{1}$/

      # If does not end in ,00 / .00 then add trailing 00 to turn it into cents
      number << "00" unless number =~ /^.*[\.,]\d{2}$/
    end
  end
end
