module Measured::Rails::ActiveRecord
  extend ActiveSupport::Concern

  module ClassMethods
    def measured(measured_class, *fields)
      options = fields.extract_options!
      options = {}.merge(options)

      measured_class = measured_class.constantize if measured_class.is_a?(String)

      raise Measured::Rails::Error, "Expecting #{ measured_class } to be a subclass of Measured::Measurable" if !measured_class.is_a?(Class) || !measured_class.ancestors.include?(Measured::Measurable)

      options[:class] = measured_class

      fields.map(&:to_sym).each do |field|
        raise Measured::Rails::Error, "The field #{ field } has already been measured" if measured_fields.keys.include?(field)
        measured_fields[field] = options

        # Reader to retrieve measured object
        define_method(field) do
          value = public_send("#{ field }_value")
          unit = public_send("#{ field }_unit")

          return nil unless value && unit

          instance = instance_variable_get("@measured_#{ field }")
          new_instance = begin
            measured_class.new(value, unit)
          rescue Measured::UnitError
            nil
          end

          if instance && instance == new_instance
            instance
          else
            instance_variable_set("@measured_#{ field }", new_instance)
          end
        end

        # Writer to assign measured object
        define_method("#{ field }=") do |incoming|
          if incoming.is_a?(measured_class)
            instance_variable_set("@measured_#{ field }", incoming)
            value_field_name = "#{ field }_value"
            precision = self.column_for_attribute(value_field_name).precision
            scale = self.column_for_attribute(value_field_name).scale
            rounded_to_scale_value = incoming.value.round(scale)
            ## For BigDecimal#split syntax, refer http://ruby-doc.org/stdlib-2.1.1/libdoc/bigdecimal/rdoc/BigDecimal.html#method-i-split
            if rounded_to_scale_value.split[1].size > precision
              raise Measured::Rails::Error, "The value #{rounded_to_scale_value} being set for column '#{value_field_name}' has too many significant digits. Please ensure it has no more than #{precision} significant digits."
            end
            public_send("#{ value_field_name }=", rounded_to_scale_value)
            public_send("#{ field }_unit=", incoming.unit)
          else
            instance_variable_set("@measured_#{ field }", nil)
            public_send("#{ field }_value=", nil)
            public_send("#{ field }_unit=", nil)
          end
        end

        # Writer to override unit assignment
        define_method("#{ field }_unit=") do |incoming|
          incoming = measured_class.conversion.to_unit_name(incoming) if measured_class.valid_unit?(incoming)
          write_attribute("#{ field }_unit", incoming)
        end

      end
    end

    def measured_fields
      @measured_fields ||= {}
    end

  end
end

ActiveSupport.on_load(:active_record) do
  ::ActiveRecord::Base.send :include, Measured::Rails::ActiveRecord
end