require 'big_money'
require 'dm-core'

module DataMapper
  module Money
    def self.included(model)
      model.extend ClassMethods
    end

    module ClassMethods

      # Add a BigMoney property.
      #
      # ==== Parameters
      # name<Symbol>:: Name of the composite money property.
      # options<Hash(Symbol => String)>:: DataMapper property options.
      #
      # ==== Examples
      # class Bike
      #   include DataMapper::Resource
      #   # ... other properties.
      #
      #   money :gst
      #   # public
      #   #   gst #=> BigMoney
      #   #   gst=(value)
      #   #
      #   # property :gst_amount,   BigDecimal, accessor: private
      #   # property :gst_currency, String,     accessor: private, length: 3
      #
      #   money :price, required: true, accessor: protected
      #   # protected
      #   #   price #=> BigMoney
      #   #   price=(value)
      #   #
      #   # property :price_amount,   BigDecimal, accessor: private, required: true
      #   # property :price_currency, String,     accessor: private, required: true, length: 3
      # end
      #--
      # TODO: Validations.
      def money(name, options = {})
        raise ArgumentError.new('You need to pass at least one argument') if name.empty?

        property               = Property.new(self, name, BigDecimal, options)
        name                   = property.name.to_s
        instance_variable_name = property.instance_variable_name
        name_amount            = "#{name}_amount"
        name_currency          = "#{name}_currency"

        self.property name_amount.to_sym,   BigDecimal, property.options.except(:accessor, :reader, :writer).merge(:accessor => :private)
        self.property name_currency.to_sym, String,     :accessor => :private, :required => property.required, :length => 3

        # TODO: Access amount, currency via readers or properties?
        # TODO: Validations or error message attempting to set with something other than BigMoney?
        class_eval <<-RUBY, __FILE__, __LINE__ + 1
          #{property.reader_visibility}
          def #{name}
            return #{instance_variable_name} if defined?(#{instance_variable_name})
            return unless #{name_amount} && #{name_currency}
            #{instance_variable_name} = BigMoney.new(#{name_amount}, #{name_currency})
          end

          #{property.writer_visibility}
          def #{name}=(value)
            raise TypeError.new('Expected BigMoney +value+ but got \#{value.class}') unless value.kind_of?(BigMoney)
            self.#{name_amount}       = value.amount
            self.#{name_currency}     = value.currency
            #{instance_variable_name} = value
          end
        RUBY
      end

    end # ClassMethods

    Model.append_inclusions self
  end # Money
end # DataMapper