# coding: utf-8
require 'bigdecimal'

class BigMoney

  # == Synopsis
  #
  #   require 'big_money'
  #   require 'big_money/exchange/yahoo' # Use yahoo finance exchange service.
  #
  #   bm = BigMoney.new('3.99')
  #   bm.amount   #=> BigDecimal.new('3.99')
  #   bm.currency #=> BigMoney::Currency::USD.instance
  #
  #   bm2 = bm.exchange(:aud)
  #   bm.amount   #=> BigDecimal.new('5.22')
  #   bm.currency #=> BigMoney::Currency::AUD.instance
  module Exchangeable
    # Exchange to a new Currency.
    #
    # ==== Examples
    #
    #   BigMoney.new(12.50, :aud).exchange(:usd)
    #
    # ==== Parameters
    # from<BigMoney::Currency, #to_s>:: Anything that BigMoney::Currency#find can find.
    #
    # ==== Returns
    # BigMoney
    def exchange(to)
      ex = amount * Exchange.rate(currency, to)
      self.class.new(ex, to)
    end
  end # Exchangeable
  include Exchangeable

  # Find the exchange rate between two currencies.
  #
  # Be aware no caching is done at all at the moment.
  #--
  # TODO: Moneta would be ideal for this.
  class Exchange
    class ConversionError < StandardError; end

    class << self
      @@services = []
      def inherited(service) #:nodoc:
        @@services << service
      end

      # Fetch the exchange rate between two currencies.
      #
      # ==== Parameters
      # from<BigMoney::Currency, #to_s>:: Anything that BigMoney::Currency#find can find.
      # to<BigMoney::Currency, #to_s>:: Anything that BigMoney::Currency#find can find.
      #
      # ==== Returns
      # BigDecimal
      def rate(from, to)
        exchange = [from, to].map{|c| Currency.find(c)}
        return BigDecimal(1.to_s) if exchange.uniq.length == 1

        service = @@services.reverse.find do |service|
          !!exchange.reject{|c| service.currencies.include?(c)}
        end

        service or raise ConversionError # TODO: Message?
        BigDecimal.new(service.read_rate(*exchange).to_s)
      end

      protected
        # Exchange rate from the first currency to the second.
        #
        # ==== Notes
        # Abstract.
        #
        # ==== Parameters
        # from<BigMoney::Currency>::
        # to<BigMoney::Currency>::
        #
        # ==== Returns
        # BigDecimal
        def read_rate(from, to)
          raise NotImplementedError
        end

        # Supported currencies.
        #
        # ==== Notes
        # Abstract.
        #
        # ==== Returns
        # Array<BigMoney::Currency, #to_s>:: Anything that BigMoney::Currency#find can find.
        def currencies
          raise NotImplementedError
        end
    end
  end # Exchange

end # Money