# = paramix.rb
#
# == Copyright (c) 2005 Thomas Sawyer, George Moschovitis
#
#   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.
#
# == Author(s)
#
# * Thomas Sawyer
# * George Moschovitis

# Author::    Thomas Sawyer, George Moschovitis
# Copyright:: Copyright (c) 2005 Thomas Sawyer, George Moschovitis
# License::   Ruby License

require 'facets/core/module/basename'

# = Parametric Mixins
#
# Parametric Mixins provides parameters for mixin modules.
# Module parameters can be set at the time of inclusion or extension,
# then accessed via an instance method of the same name as the included
# module.
#
# == Synopsis
#
#   module Mixin
#     def hello
#       puts "Hello from #{Mixin(:name)}"
#     end
#   end
#
#   class MyClass
#     include Mixin, :name => 'Ruby'
#   end
#
#   m = MyClass.new
#   m.hello -> 'Hello from Ruby'
#
# You can view the full set of parameters via the #mixin_parameters
# class method, which returns a hash keyed on the included modules.
#
#   MyClass.mixin_parameters         #=> {Mixin=>{:name=>'Ruby'}}
#   MyClass.mixin_parameters[Mixin]  #=> {:name=>'Ruby'}
#
# To create _dynamic mixins_ you can use the #included callback
# method along with mixin_parameters method like so:
#
#   module Mixin
#     def self.included( base )
#       parms = base.mixin_parameters[self]
#       base.class_eval {
#         def hello
#           puts "Hello from #{parms(:name)}"
#         end
#       }
#     end
#   end
#
# More conveniently a new callback has been added, #included_with_parameters,
# which passes in the parameters in addition to the base class/module.
#
#   module Mixin
#     def self.included_with_parameters( base, parms )
#       base.class_eval {
#         def hello
#           puts "Hello from #{parms(:name)}"
#         end
#       }
#     end
#   end
#
# We would prefer to have passed the parameters through the #included callback
# method itself, but implementation of such a feature is much more complicated.
# If a reasonable solution presents itself in the future however, we will fix.

class Module

  # Store for module parameters. This is local per module
  # and indexed on module/class included-into.
  def mixin_parameters ; @mixin_parameters ||= {} ; end

  alias_method :include_without_parameters, :include

  def include(*args)
    params = args.last.is_a?(Hash) ? args.pop : {}
    args.each do |mod|
      mixin_parameters[mod] = params
      if mod.basename
        define_method( mod.basename ) do |key|
          if params.key?(key)
            params[key]
          else
            super if defined?( super )
          end
        end
      end
    end
    r = include_without_parameters(*args)
    for mod in args
      if mod.respond_to?(:included_with_parameters)
        mod.included_with_parameters( self, params )
      end
    end
    r
  end

  alias_method :extend_without_parameters, :extend

  def extend(*args)
    params = args.last.is_a?(Hash) ? args.pop : {}
    args.each do |mod|
      (class << self; self; end).class_eval do
        mixin_parameters[mod] = params
        if mod.basename
          define_method( mod.basename ) do |key|
            if params.key?(key)
              params[key]
            else
              super if defined?( super )
            end
          end
        end
      end
    end
    r = extend_without_parameters(*args)
    for mod in args
      if mod.method_defined?(:extended_with_parameters)
        mod.extended_with_parameters( self, params )
      end
    end
    r
  end

end



#  _____         _
# |_   _|__  ___| |_
#   | |/ _ \/ __| __|
#   | |  __/\__ \ |_
#   |_|\___||___/\__|
#

=begin test

  require 'test/unit'
  require 'facets/core/kernel/eigenclass'

  class TC01 < Test::Unit::TestCase

    module M
      def f
        M(:p)
      end
      def self.included_with_parameters( base, parms )
        base.class_eval do
          define_method :check do
            parms
          end
        end
      end
    end

    class C
      include M, :p => "check"
    end

    class D
      include M, :p => "steak"
    end

    def test_01_001
      c = C.new
      assert_equal( "check", c.M(:p) )
      assert_equal( "check", c.f )
    end

    def test_01_002
      d = D.new
      assert_equal( "steak", d.M(:p) )
      assert_equal( "steak", d.f )
    end

    def test_01_003
      assert_equal( {M=>{:p => "check"}}, C.mixin_parameters )
      assert_equal( {M=>{:p => "steak"}}, D.mixin_parameters )
    end

    def test_01_004
      c = C.new
      assert_equal( {:p => "check"}, c.check )
      d = D.new
      assert_equal( {:p => "steak"}, d.check )
    end

  end


  class TC02 < Test::Unit::TestCase

    module M
      def f
        M(:p)
      end
    end

    class C
      extend M, :p => "mosh"
    end

    class D
      extend M, :p => "many"
    end

    def test_02_001
      assert_equal( "mosh", C.f )
    end

    def test_02_002
      assert_equal( "many", D.f )
    end

    def test_02_003
      assert_equal( {M=>{:p => "mosh"}}, C.eigenclass.mixin_parameters )
      assert_equal( {M=>{:p => "many"}}, D.eigenclass.mixin_parameters )
    end

  end

=end
