######################################
# WARNING! Highly expiremental code! #
######################################

# = instance_interception.rb
#
# == Copyright (c) 2005 Thomas Sawyer
#
#   Ruby License
#
#   This module is free software. You may use, modify, and/or redistribute this
#   software under the same terms as Ruby.
#
#   This program is distributed in the hope that it will be useful, but WITHOUT
#   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#   FOR A PARTICULAR PURPOSE.
#
# == Authors & Contributors
#
# * Thomas Sawyer

#require 'facets/core/module/alias_method_chain'

# = Instance Interception
#
# This code is in the spirit of class_extension, but performs method
# instance level method interception instead of class level method inheritance.

class Module

  def instance_interception(&block)
    @instance_interception ||= Module.new do
      def self.append_features(mod)
        append_features_without_instance_interception( mod )
      end
    end
    @instance_interception.module_eval(&block) if block_given?
    @instance_interception
  end

  private :instance_interception

  alias_method :append_features_without_instance_interception, :append_features

  # Append features

  def append_features( mod )

    aspect = instance_interception
    aspect.__send__( :append_features_without_instance_interception, mod )

    aspect.instance_methods.each do |meth|
      if mod.method_defined?( meth )
        aspect.advise( mod, meth )
      end
    end

    append_features_without_instance_interception( mod )

    #if mod.instance_of? Module
    aspect.__send__( :append_features_without_instance_interception, mod.__send__(:instance_interception) )
    #end

  end

  # Apply the around advice.

  def advise( mod, meth )
    advice = instance_method( meth )
    instance_target = mod.instance_method(meth)
    mod.__send__( :define_method, meth ) { |*args| #, &blk|
      target = instance_target.bind( self )
      (class << target; self; end).class_eval { define_method( :super ){ call( *args ) } }
      advice.bind( self ).call( target, *args ) #, &blk )
    }
  end

  # TODO make method_added hook more robust so not as to clobber others.
  # If a method is added to the module/class that is advised.

  def method_added( meth )
    return if @method_added_short
    if instance_interception.method_defined?( meth )
      include instance_interception
      @method_added_short = true
      instance_interception.advise( self, meth )
      @method_added_short = false
    end
  end

end



=begin test

  require 'test/unit'

  class TestModule < Test::Unit::TestCase

    module A

      def f ; "F" ; end
      def g ; "G" ; end

      instance_interception do
        def f( target, *args, &blk )
          '{' + target.super + '}'
        end
        def g( target, *args, &blk )
          '{' + target.super + '}'
        end
      end

    end

    class X
      def f ; super ; end
      include A
      def g ; super ; end
    end

    def test_1_01
      x = X.new
      assert_equal( "{F}", x.f )
      assert_equal( "{G}", x.g )
    end

  end

=end
