module Observable
  module ClassMethods
    def def_observables ob, *methods, &block
      methods.each do |m|
        def_observable ob, m, m, &block
      end
    end
    def def_observable ob, method, ali=method, &block
      if ob == :self and ali == method
        alias_method :"__observed_#{method}", method
        method = :"__observed_#{method}"
      end
      module_eval <<-EOS, "(__OBSERVER__)", 1
        def #{ali} *args, &block
          r = #{ob}.__send__ :"#{method}", *args, &block
          __observer_notify :#{ali}, *args
          r
        end
      EOS
    end
  end

  module InstanceMethods
    def __observer_notify meth, *args
      (__observers[meth] + __observers[0]).each do |b|
        if b.kind_of? Symbol
          b = instance_method(b).bind(self)
        end
        l = b.arity >= 0 ? b.arity : args.length
        b.call *[meth, *args][0, l]
      end
    end

    def __observers
      @__observers ||= Hash.new {|h,k| h[k] = []}
    end

    def add_observation *methods, &block
      methods.each do |m|
        __observers[m] << block
      end
      __observers[0] << block if methods.empty?
    end
  end

  def self.included mod
    mod.send :include, InstanceMethods
    mod.send :extend, ClassMethods
  end
end
